I have come across this particular snippet of code many times in solutions of competitive programming contests. I understand the basic use of this code to beat time limits b
try this for faster I/O
ios_base::sync_with_stdio(false); cin.tie(NULL);
It sets whether the standard C++ streams are synchronized to the standard C streams after each input/output operation. By default, iostream objects and cstdio streams are synchronized.
In the PrintUint
function, he's basically just unrolling a loop by hand. Unrolling loops are sometimes a good thing to do -- however the compiler already does it, and will do it better than you, most of the time.
To plug my favorite language feature, it would be better implemented using templates: a simple implementation (more clever probably exist) would look like:
// I'm sure the compiler can figure out the inline part, but I'll add it anyways
template<unsigned int N>
inline void print_uint_inner(uint32_t v) {
m_data[m_dataOffset + N] = v - v / 10 * 10 + 48;
print_uint_inner<N-1>(v / 10);
}
// For situations just like this, there's a trick to avoid having to define the base case separately.
inline void print_uint_inner<0>(uint32_t v) {
m_data[m_dataOffset] = v - v / 10 * 10 + 48;
}
template<unsigned int N>
inline void print_uint_helper(uint32_t v) {
print_uint_inner<N-1>(v);
m_dataOffset += N;
}
// We could generate the compile-time binary search with templates too, rather than by hand.
void PrintUint(uint32_t v, char d) {
if (m_dataOffset + 11 > sizeof(m_data)) Flush();
if (v < 100000) {
if (v < 1000) {
if (v < 10) {
print_uint_helper<1>(v);
} else if (v < 100) {
print_uint_helper<2>(v);
} else {
print_uint_helper<3>(v);
}
} else {
if (v < 10000) {
print_uint_helper<4>(v);
} else {
print_uint_helper<5>(v);
}
}
} else {
if (v < 100000000) {
if (v < 1000000) {
print_uint_helper<6>(v);
} else if (v < 10000000) {
print_uint_helper<7>(v);
} else {
print_uint_helper<8>(v);
}
} else {
if (v < 1000000000) {
print_uint_helper<9>(v);
} else {
print_uint_helper<10>(v);
}
}
}
m_data[m_dataOffset++] = d;
}
Is doing things like this good coding practice in general? Yes, but only if all of the following criteria are satisfied:
Also, you should probably retain the ability to switch back to the simple version, either using compile-time constants or pre-processor directives. This will be important for two reasons:
Is it good practice to employ similar techniques in production level code?
No. Reimplementing the wheel leads to bugs. Bugs require extra development time and cost money.
can help me understand it further.
If you don't understand the code, the code is poorly written. Code is written by humans, and for humans. If another programmer doesn't understand code quickly, there may be a big problem. The rationale behind this thinking ("written for humans") is simple: development time costs a lot, and unreadable code increases development time.
The code fragment in question utilizes several bad coding practices:
m_v
means without reading the rest of the program, for example?)+ 48
, + 11
)v /= 10
and similar - C++ has macros/templates, damn it, so if you want to unroll loop by hand, use them!). Unless you want to become worse at programming, I'd advise to avoid trying to "understand" this code fragment. It is bad.
I seriously doubt that this particular design was a result of profiling. Most likely scenario some "genius" assumed that his code fragment will outperform built-in functions.
When you want performance, you follow this pattern: