How can I make DRY (lacks repetitive patterns) code that also avoids inefficiencies from using intermediate values in a calculation that might not need to be used?
Here is an example:
In this code, I am using an octree to store a region of 32x32x32 voxels, and I need a function to get and set any given one of the voxels. The get function involves descending the tree until a leaf node is encountered.
The set function involves doing the same, except an array of all previous offsets need to be recorded, so that if a branch is inserted or deleted in the middle of the tree, the indices in parent branches can be updated when the memory is shift to make more space or to fill the gap.
typedef struct Octree {
Ptr data; // Definition of Ptr and Node8 are not relevant to this question
uint8_t data_alloc, set_alloc;
uint16_t data_size, set_size, base;
Node8 set[];
} Octree;
static uint_fast8_t octree_index(const uint_fast8_t x, const uint_fast8_t z, const uint_fast8_t y, const uint_fast8_t level) {
return (x>>level&1)|(z>>level&1)<<1|(y>>level&1)<<2;
}
uint16_t octree_get(Octree *octree, const uint_fast8_t x, const uint_fast8_t z, const uint_fast8_t y) { // Gets the value at a certain position in the octree
uint_fast16_t set = 0;
uint_fast8_t level = 5;
uint16_t node = octree->base;
while (node&0x8000 && level--)
node = ((uint16_t *)octree->set)[(set += node&0x7FFF)<<3|octree_index(x, y, z, level)];
return node;
}
Octree *octree_set(Octree *octree, const uint_fast8_t x, const uint_fast8_t z, const uint_fast8_t y, const uint16_t new) { // Gets the value at a certain position in the octree
uint_fast16_t set = 0, stack[5]; // Note the new `stack` variable
uint_fast8_t level = 5;
uint16_t node = octree->base;
while (node&0x8000 && level--)
node = ((uint16_t *)octree->set)[stack[level] = (set += node&0x7FFF)<<3|octree_index(x, y, z, level)];
// The only difference in the above line is the `stack[level] = ` before `(set += ...`
if (node != new) {
if (level) {
// Subdivide and insert
} else {
// Set and merge if necessary
}
}
return octree;
}
The above code is repetitive, but more 'efficient' since the octree_get
function does not save the intermediate values that do not need to be saved. A less repetitive, but less 'efficient' way is to use:
typedef struct Stack {
uint16_t node;
uint_fast8_t level;
uint_fast16_t offset[5];
} Stack;
Stack octree_get(Octree *octree, const uint_fast8_t x, const uint_fast8_t z, const uint_fast8_t y) {
uint_fast16_t set = 0;
Stack stack = {octree->base, 5};
while (stack.node&0x8000 && stack.level--)
stack.node = ((uint16_t *)octree->set)[stack.offset[stack.level] = (set += stack.node&0x7FFF)<<3|octree_index(x, y, z, stack.level)];
return stack;
} // Returning a struct in and of itself may or may not be optimal but that is arguably a separate question
Octree *octree_set(Octree *octree, const uint_fast8_t x, const uint_fast8_t z, const uint_fast8_t y, const uint16_t new) {
Stack stack = octree_get(octree, x, y, z);
if (stack.node != new) {
if (stack.level) {
// Subdivide and insert
} else {
// Set and merge if necessary
}
}
return octree;
}
In the above solution, the traversal code is not duplicated, but getting a value saves said stack of values, even if not needed later. Keep in mind that this is just an example, but my question involves any variation of such problems where there is a complex calculation and one or more intermediate values may or may not be needed at a later point.
Does anyone know the most efficient way (perhaps eliminating the unnecessary array creation and stores) of doing this while still not duplicating the code? I have thought long and hard about this, and this seems to be a space/time tradeoff. Can someone prove me wrong and find a way that is both non-repetitive as in the second example, but as 'efficient' as the first example?