Fast input/output in competitive programming

后端 未结 3 1012
南笙
南笙 2020-12-24 09:23

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

3条回答
  •  伪装坚强ぢ
    2020-12-24 09:36

    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 
    inline void print_uint_inner(uint32_t v) {
        m_data[m_dataOffset + N] = v - v / 10 * 10 + 48;
        print_uint_inner(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
    inline void print_uint_helper(uint32_t v) {
        print_uint_inner(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:

    • You've already written the obvious, easy to understand, simple version.
    • You've profiled your program, so that you know this stretch of code is costing enough time to be worth the effort
    • You're willing to go through the extra work to ensure the more complex version is actually correct
    • You've profiled the revised program, so that you know the rewrite actually improved your run-time.

    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:

    • When you're debugging, the ability to switch back to the simple version will help to narrow down places where there could be problems
    • When you try running on a different computer (or even the same computer under different conditions), you may find the complicated version is no longer faster than the simple version.

提交回复
热议问题