4 deleted 2 characters in body edited Dec 11 '17 at 23:16 user204677 Simplest one to me is using the stack when possible whenever a common case use pattern fits a range of, say, [0, 64) but has rare cases that have no small upper bound. Simple C example (before): ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int* values = calloc(n, sizeof(int)); // do stuff with values ... free(values); } `````` And after: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int values_mem[64] = {...0} int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int)); // do stuff with values ... if (values != values_mem) free(values); } `````` I've generalized this like so since these kinds of hotspots crop up a lot in profiling: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. MemFast values_mem; int* values = mf_calloc(&values_mem, n, sizeof(int)); // do stuff with values ... mf_free(&values_mem); } `````` The above uses the stack when the data being allocated is small enough in those 99.9% cases, and uses the heap otherwise. In C++ I've generalized this with a standard-compliant small sequence (similar to `SmallVector` implementations out there) which revolves around the same concept. It's not an epic optimization (I often getI've gotten reductions from, say, 3 seconds for an operation to complete down to 1.8 seconds), but it requires such trivial effort to apply. When you can get something down from 3 seconds to 1.8 seconds by just introducing a line of code and changing two, it's a pretty good bang for such a small buck. Simplest one to me is using the stack when possible whenever a common case use pattern fits a range of, say, [0, 64) but has rare cases that have no small upper bound. Simple C example (before): ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int* values = calloc(n, sizeof(int)); // do stuff with values ... free(values); } `````` And after: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int values_mem[64] = {...} int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int)); // do stuff with values ... if (values != values_mem) free(values); } `````` I've generalized this like so since these kinds of hotspots crop up a lot in profiling: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. MemFast values_mem; int* values = mf_calloc(&values_mem, n, sizeof(int)); // do stuff with values ... mf_free(&values_mem); } `````` The above uses the stack when the data being allocated is small enough in those 99.9% cases, and uses the heap otherwise. In C++ I've generalized this with a standard-compliant small sequence (similar to `SmallVector` implementations out there) which revolves around the same concept. It's not an epic optimization (I often get reductions from, say, 3 seconds for an operation to complete down to 1.8 seconds), but it requires such trivial effort to apply. When you can get something down from 3 seconds to 1.8 seconds by just introducing a line of code and changing two, it's a pretty good bang for such a small buck. Simplest one to me is using the stack when possible whenever a common case use pattern fits a range of, say, [0, 64) but has rare cases that have no small upper bound. Simple C example (before): ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int* values = calloc(n, sizeof(int)); // do stuff with values ... free(values); } `````` And after: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int values_mem[64] = {0} int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int)); // do stuff with values ... if (values != values_mem) free(values); } `````` I've generalized this like so since these kinds of hotspots crop up a lot in profiling: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. MemFast values_mem; int* values = mf_calloc(&values_mem, n, sizeof(int)); // do stuff with values ... mf_free(&values_mem); } `````` The above uses the stack when the data being allocated is small enough in those 99.9% cases, and uses the heap otherwise. In C++ I've generalized this with a standard-compliant small sequence (similar to `SmallVector` implementations out there) which revolves around the same concept. It's not an epic optimization (I've gotten reductions from, say, 3 seconds for an operation to complete down to 1.8 seconds), but it requires such trivial effort to apply. When you can get something down from 3 seconds to 1.8 seconds by just introducing a line of code and changing two, it's a pretty good bang for such a small buck. 3 added 16 characters in body edited Dec 10 '17 at 22:24 user204677 Simplest one to me is using the stack when possible whenever a common case use pattern fits a range of, say, [0, 64) but has rare cases that have no small upper bound. Simple C example (before): ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int* values = calloc(n, sizeof(int)); // do stuff with values ... free(values); } `````` And after: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int values_mem[64] = {...} int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int)); // do stuff with values ... if (values != values_mem) free(values); } `````` I've generalized this like so since these kinds of hotspots crop up a lot in profiling: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. MemFast values_mem; int* values = mf_mallocmf_calloc(&values_mem, n, sizeof(int)); // do stuff with values ... mf_free(&values_mem); } `````` The above uses the stack when the data being allocated is small enough in those 99.9% cases, and uses the heap otherwise. In C++ I've generalized this with a standard-compliant small sequence (similar to `SmallVector` implementations out there) which revolves around the same concept. It's not an epic optimization (I often get reductions from, say, 3 seconds for an operation to complete down to 1.8 seconds), but it requires such trivial effort to apply. When you can get something down from 3 seconds to 1.8 seconds by just introducing a line of code and changing two, it's a pretty good bang for such a small buck. Simplest one to me is using the stack when possible whenever a common case use pattern fits a range of, say, [0, 64) but has rare cases that have no small upper bound. Simple C example (before): ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int* values = calloc(n, sizeof(int)); // do stuff with values ... free(values); } `````` And after: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int values_mem[64] = {...} int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int)); // do stuff with values ... if (values != values_mem) free(values); } `````` I've generalized this like so since these kinds of hotspots crop up a lot in profiling: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. MemFast values_mem; int* values = mf_malloc(&values_mem); // do stuff with values ... mf_free(&values_mem); } `````` The above uses the stack when the data being allocated is small enough in those 99.9% cases, and uses the heap otherwise. In C++ I've generalized this with a standard-compliant small sequence (similar to `SmallVector` implementations out there) which revolves around the same concept. It's not an epic optimization (I often get reductions from, say, 3 seconds for an operation to complete down to 1.8 seconds), but it requires such trivial effort to apply. When you can get something down from 3 seconds to 1.8 seconds by just introducing a line of code and changing two, it's a pretty good bang for such a small buck. Simplest one to me is using the stack when possible whenever a common case use pattern fits a range of, say, [0, 64) but has rare cases that have no small upper bound. Simple C example (before): ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int* values = calloc(n, sizeof(int)); // do stuff with values ... free(values); } `````` And after: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int values_mem[64] = {...} int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int)); // do stuff with values ... if (values != values_mem) free(values); } `````` I've generalized this like so since these kinds of hotspots crop up a lot in profiling: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. MemFast values_mem; int* values = mf_calloc(&values_mem, n, sizeof(int)); // do stuff with values ... mf_free(&values_mem); } `````` The above uses the stack when the data being allocated is small enough in those 99.9% cases, and uses the heap otherwise. In C++ I've generalized this with a standard-compliant small sequence (similar to `SmallVector` implementations out there) which revolves around the same concept. It's not an epic optimization (I often get reductions from, say, 3 seconds for an operation to complete down to 1.8 seconds), but it requires such trivial effort to apply. When you can get something down from 3 seconds to 1.8 seconds by just introducing a line of code and changing two, it's a pretty good bang for such a small buck. 2 added 162 characters in body edited Dec 10 '17 at 22:19 user204677 Simplest one to me is using the stack when possible whenever a common case use pattern fits a range of, say, [0, 64) but has rare cases that have no small upper bound. Simple C example (before): ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int* values = calloc(n, sizeof(int)); // do stuff with values ... free(values); } `````` And after: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int values_mem[64] = {...} int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int)); // do stuff with values ... if (values != values_mem) free(values); } `````` I've generalized this like so since these kinds of hotspots crop up a lot in profiling: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. MemFast values_mem; int* values = mf_malloc(&values_mem); // do stuff with values ... mf_free(&values_mem); } `````` The above uses the stack when the data being allocated is small enough in those 99.9% cases, and uses the heap otherwise. In C++ I've generalized this with a standard-compliant small sequence (similar to `SmallVector` implementations out there) which revolves around the same concept. It's not an epic optimization (I often get reductions from, say, 3 seconds for an operation to complete down to 1.8 seconds), but it requires such trivial effort to apply. When you can get something down from 3 seconds to 1.8 seconds by just introducing a line of code and changing two, it's a pretty good bang for such a small buck. Simplest one to me is using the stack when possible whenever a common case use pattern fits a range of, say, [0, 64) but has rare cases that have no small upper bound. Simple C example (before): ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int* values = calloc(n, sizeof(int)); // do stuff with values ... free(values); } `````` And after: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int values_mem[64] = {...} int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int)); // do stuff with values ... if (values != values_mem) free(values); } `````` I've generalized this like so since these kinds of hotspots crop up a lot in profiling: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. MemFast values_mem; int* values = mf_malloc(&values_mem); // do stuff with values ... mf_free(&values_mem); } `````` The above uses the stack when the data being allocated is small enough in those 99.9% cases, and uses the heap otherwise. In C++ I've generalized this with a standard-compliant small sequence (similar to `SmallVector` implementations out there) which revolves around the same concept. It's not an epic optimization (I often get reductions from, say, 3 seconds for an operation to complete down to 1.8 seconds), but it requires such trivial effort to apply. Simplest one to me is using the stack when possible whenever a common case use pattern fits a range of, say, [0, 64) but has rare cases that have no small upper bound. Simple C example (before): ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int* values = calloc(n, sizeof(int)); // do stuff with values ... free(values); } `````` And after: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. int values_mem[64] = {...} int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int)); // do stuff with values ... if (values != values_mem) free(values); } `````` I've generalized this like so since these kinds of hotspots crop up a lot in profiling: ``````void some_hotspot_called_in_big_loops(int n, ...) { // 'n' is, 99% of the time, <= 64. MemFast values_mem; int* values = mf_malloc(&values_mem); // do stuff with values ... mf_free(&values_mem); } `````` The above uses the stack when the data being allocated is small enough in those 99.9% cases, and uses the heap otherwise. In C++ I've generalized this with a standard-compliant small sequence (similar to `SmallVector` implementations out there) which revolves around the same concept. It's not an epic optimization (I often get reductions from, say, 3 seconds for an operation to complete down to 1.8 seconds), but it requires such trivial effort to apply. When you can get something down from 3 seconds to 1.8 seconds by just introducing a line of code and changing two, it's a pretty good bang for such a small buck. 1 answered Dec 10 '17 at 22:13 user204677