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) {
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?
octree_set()
is a member ofOctree
and thus perfectly entitled to access its private data members. So, nothing to fuzz about. I would only add a comment before thewhile()
loop inoctree_set()
saying something along the lines of//record the path to the leaf node at (x, y, z)
. Because that's what you are doing. You might even want to create a private member functionoctree_getPath()
, but that's unnecessary imho.set
variable is not used, this is not repetition, it should be removed. Thestack
is the required result of the lookup, as well as thenode
variable, so they cannot be folded into a function call and do not count as repetition. Thelevel
variable is just a loop index that should have been written inside afor()
initialization statement, imho, it's an integral part of the loop itself. If I count that as a repetition (which I didn't do), it's still only three lines of repetition.octree_get
intooctree_set
and then optimize away any unnecessary operations.