【问题标题】:Optimization of loops and if优化循环和 if
【发布时间】:2014-11-09 19:28:27
【问题描述】:

我的程序如下所示:

void Process1(unsigned char* data)
{

}
void Process2(unsigned char* data)
{

}
void Process3(unsigned char* data)
{

}

#define FLAG1 (1 << 1)
#define FLAG2 (1 << 2)
#define FLAG3 (1 << 3)

void ProcessData(unsigned char* data, unsigned int bytes, unsigned int flags)
{
    bool b1 = !!(flags & FLAG1);
    bool b2 = !!(flags & FLAG2);
    bool b3 = !!(flags & FLAG3);
    for (unsigned int i = 0; i < bytes; i ++)
    {
        if (b1) Process1(data + i);
        if (b2) Process2(data + i);
        if (b3) Process3(data + i);
    }
}

看起来,flags &amp; FLAG1 A.K.A b1 在所有循环中都不会改变。但是我们仍然必须在每个循环中进行分支。我只是想知道是否有办法动态避免这种不必要的分支。

这是 Lundin 解决方案的演示。

#include <windows.h>
#include <stdio.h>
#include <time.h>
LARGE_INTEGER ls, le, ll;
#define START_CLOCK() QueryPerformanceCounter(&ls)
#define END_CLOCK() printf ("%.0lf ns\n", (QueryPerformanceCounter(&le), ((double)le.QuadPart - ls.QuadPart) / ll.QuadPart * 1000000));


void Process1(unsigned char* data)
{
    (*data)++;
}
void Process2(unsigned char* data)
{
    (*data)--;
}
void Process3(unsigned char* data)
{
    (*data) *= (*data);
}

#define FLAG1 (1 << 1)
#define FLAG2 (1 << 2)
#define FLAG3 (1 << 3)

void ProcessData(unsigned char* data, unsigned int bytes, unsigned int flags)
{
    bool b1 = !!(flags & FLAG1);
    bool b2 = !!(flags & FLAG2);
    bool b3 = !!(flags & FLAG3);
    for (unsigned int i = 0; i < bytes; i ++)
    {
        if (b1) Process1(data + i);
        if (b2) Process2(data + i);
        if (b3) Process3(data + i);
    }
}


typedef void (*proc_t)(unsigned char*);

inline static void do_nothing (unsigned char* ptr)
{
    (void)ptr;
}

void ProcessData_x(unsigned char* data, unsigned int bytes, unsigned int flags)
{
    bool b1 = (flags & FLAG1) != 0;  // de-obfuscate the boolean logic
    bool b2 = (flags & FLAG2) != 0;
    bool b3 = (flags & FLAG3) != 0;

    proc_t p1 = b1 ? Process1 : do_nothing;
    proc_t p2 = b2 ? Process2 : do_nothing;
    proc_t p3 = b3 ? Process3 : do_nothing;

    for (unsigned int i = 0; i<bytes; i++)
    {
        p1(data + i);
        p2(data + i);
        p3(data + i);
    }
}

int main()
{
    if (!QueryPerformanceFrequency(&ll)) return 1;

    const unsigned int bytes = 0xffff;
    srand((unsigned int)time(NULL));
    unsigned int flags = rand() & 0x7;
    unsigned char* data = new unsigned char[bytes];
    for (unsigned int i = 0; i < bytes; i++)
    {
        data[i] = (unsigned char)(rand() & 0xff);
    }

    START_CLOCK();

    ProcessData(data, bytes, flags);

    END_CLOCK();

    START_CLOCK();

    ProcessData_x(data, bytes, flags);

    END_CLOCK();
}

这是输出:

134 ns
272 ns

我已经运行了好几次,但出乎意料的是,它花费了更多的时间:(..它也编译为'vs2010 Release x86'

【问题讨论】:

  • 过程像加法一样简单吗?

标签: c++ c loops optimization branch


【解决方案1】:

首先,在没有考虑特定系统的情况下谈论优化没有任何意义......

话虽如此,我会通过以下方式优化分支:

typedef void (*proc_t)(unsigned char*);

inline static void do_nothing (unsigned char* ptr)
{
    (void)ptr;
}

...

void ProcessData(unsigned char* data, unsigned int bytes, unsigned int flags)
{
    bool b1 = (flags & FLAG1) != 0;  // de-obfuscate the boolean logic
    bool b2 = (flags & FLAG2) != 0;
    bool b3 = (flags & FLAG3) != 0;

    proc_t p1 = b1 ? Process1 : do_nothing;
    proc_t p2 = b2 ? Process2 : do_nothing;
    proc_t p3 = b3 ? Process3 : do_nothing;

    for (unsigned int i = 0; i<bytes; i++)
    {
        p1(data + i);
        p2(data + i);
        p3(data + i);
    }
}

【讨论】:

  • 我认为这个解决方案很好。谢谢!顺便提一句。有什么办法可以避免#define 拨打这个do_nothing 电话吗?我知道这个函数将“几乎肯定”内联,我只想要这个looks efficient :D
  • @user694733 你可能是对的。正如我在 Release VS2010 中编译的那样,do_nothing 函数在 asm 中仍然是 call
  • @JimYang 但是根据要求,循环中没有分支。
  • @JimYang "compiled in Release VS2010" 这些优化在 PC 上没有多大意义,它属于“未成熟优化”的范畴。这里和那里的几个 CPU 滴答声都无关紧要,因为 PC 已经获得了不存在的实时性能。您最好将时间花在编写实际应用程序上。
【解决方案2】:

一个 c++ 解决方案。类似于 Lundin 的答案,但没有调用空函数。我不确定这是否会对性能产生任何影响,主要优点是您不需要手动列出循环中的所有进程调用。如果你想微优化或者想要c,你可以使用堆栈上的数组,但你必须自己管理一些计数器。

typedef void (*proc_t)(unsigned char*);
std::vector<proc_t> processes;
if (b1) processes.push_back(Process1);
if (b2) processes.push_back(Process2);
if (b3) processes.push_back(Process3);

for(auto p : processes)
    for (unsigned int i = 0; i<bytes; i++)
        p(data + i);

【讨论】:

  • 谢谢,但我认为在这个级别上它不会更有效,因为它执行了另一个循环。:)
  • 我同意,这不太可能更快。但是我认为当你有很多标志并且需要进行更改时,它会更容易维护。
【解决方案3】:
    bool b1 = !!(flags & FLAG1);
    bool b2 = !!(flags & FLAG2);
    bool b3 = !!(flags & FLAG3);


    int caseNow=SelectCaseAtOnce(b1,b2,b3);

    if(caseNow==0)
        for (unsigned int i = 0; i < bytes; i ++)
        {
            Process1(data + i);

        }
     else if(caseNow==1)
        for (unsigned int i = 0; i < bytes; i ++)
        {

           Process2(data + i);

        }
     else if(caseNow==2)
        for (unsigned int i = 0; i < bytes; i ++)
        {

             Process3(data + i);
        }
    else if(caseNow==3)
        for (unsigned int i = 0; i < bytes; i ++)
        {
            Process1(data + i);
            Process2(data + i);

        }
    if(caseNow==4)
        for (unsigned int i = 0; i < bytes; i ++)
        {
             Process1(data + i);

             Process3(data + i);
        }
    else if(caseNow==5)
        for (unsigned int i = 0; i < bytes; i ++)
        {

            Process2(data + i);
            Process3(data + i);
        }
    else if(caseNow==6)
        for (unsigned int i = 0; i < bytes; i ++)
        {
            Process1(data + i);
            Process2(data + i);
            Process3(data + i);
        }
    else {}

【讨论】:

    【解决方案4】:

    这是另一种使用模板的解决方案 - 这样您将获得每个变体的内部循环的优化版本。如果 ProcessN 函数足够短/简单到值得内联,那么这可能是值得优化的。

    #include <tuple>
    #include <map>
    #include <utility>
    
    using namespace std;
    
    inline void Process1(unsigned char* data) {}
    inline void Process2(unsigned char* data) {}
    inline void Process3(unsigned char* data) {}
    
    #define FLAG1 (1 << 1)
    #define FLAG2 (1 << 2)
    #define FLAG3 (1 << 3)
    
    template <bool b1, bool b2, bool b3>
    void ProcessData(unsigned char* data, unsigned int bytes) {
        for (unsigned int i = 0; i < bytes; i++) {
            if (b1) Process1(data + i);
            if (b2) Process2(data + i);
            if (b3) Process3(data + i);
        }
    }
    
    void ProcessData(unsigned char* data, unsigned int bytes, unsigned int flags) {
        typedef void (*ProcessFunc)(unsigned char*, unsigned int bytes);
        static map<tuple<bool, bool, bool>, ProcessFunc> funcs{
            {make_tuple(false, false, false), ProcessData<false, false, false>},
            {make_tuple(false, false, true), ProcessData<false, false, true>},
            {make_tuple(false, true, false), ProcessData<false, true, false>},
            {make_tuple(false, true, true), ProcessData<false, true, true>},
            {make_tuple(true, false, false), ProcessData<true, false, false>},
            {make_tuple(true, false, true), ProcessData<true, false, true>},
            {make_tuple(true, true, false), ProcessData<true, true, false>},
            {make_tuple(true, true, true), ProcessData<true, true, true>}};
    
        bool b1 = !!(flags & FLAG1);
        bool b2 = !!(flags & FLAG2);
        bool b3 = !!(flags & FLAG3);
        funcs[make_tuple(b1, b2, b3)](data, bytes);
    }
    

    【讨论】:

    • 对不起,我不认为这种枚举可能有帮助,因为我们可能要列出多达 2^32 个案例..
    • 你真的需要每一种组合吗?您只能实例化实际需要的那些(或最常用的版本),然后回退到带有分支的通用版本。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-18
    • 2019-08-23
    • 2022-01-02
    • 2019-11-08
    • 1970-01-01
    • 2020-04-19
    • 1970-01-01
    相关资源
    最近更新 更多