【问题标题】:Is there still room for optimization in this part of the code?这部分代码还有优化的空间吗?
【发布时间】:2019-03-08 08:44:57
【问题描述】:
void HappyTest()
{
    for (int i = 0; i < 2100000000; i++) {
        int n = i;
        while (n >= 10) {
            int m = n, sum = 0;
            while (m != 0) {
                int t = m >= 10 ? m % 10 : m;
                sum += t * t;
                m /= 10;
            }
            n = sum;
        }
        //return n == 1 || n == 7;
        //if (i % 10000000 == 0) {
        //  cout << i << endl;
    }
}

VS2017 Debug Mode Performance analyzer

我用vs2017的性能分析工具拿到图中的数据,发现性能消耗主要是%和*运算。 这部分代码有优化空间吗?

【问题讨论】:

  • 如果您告诉我们您要解决什么问题,将会很有帮助。
  • 是的。该函数没有效果,所以void HappyTest() {}是一个非常好的优化。
  • 循环可以从for (int i=11...开始
  • @PaulOgilvie 我也很害怕,但是 0x7D2B7500 与 32 位上的 int 兼容
  • int t = m &gt;= 10 ? m % 10 : m; 防止在不需要时采用模数 - 以牺牲管道中断为代价?无论如何,cout 表明这不是 C 代码。

标签: c


【解决方案1】:

让我们先对您的代码稍作重写 - 比如:

int HappyTest()
{
    int n;
    for (int i = 0; i < 210000000; i++) {  // Reduced by a factor 10 compared to OP code
        n = i;
        while (n >= 10) {
            int m = n, sum = 0;
            while (m != 0) {
                int t = m >= 10 ? m % 10 : m;
                sum += t * t;
                m /= 10;
            }
            n = sum;
        }
    }
    return n;
}

int main(void) {
  printf("%d\n", HappyTest());
}

在我的系统上运行这个需要 10.7 秒。

您的代码是关于:取一个数字并计算其所有数字的平方和。

因此,您的乘法仅限于 10 种不同的乘法,即 0*0、1*1、...、9*9

因此,优化的一个想法可能是将结果放入表中并进行表查找而不是乘法。喜欢:

int mul_tab_10[10] = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81};

int HappyTest()
{
    int n;
    for (int i = 0; i < 210000000; i++) {
        n = i;
        while (n >= 10) {
            int m = n, sum = 0;
            while (m != 0) {
                int t = m >= 10 ? m % 10 : m;
                sum += mul_tab_10[t];
                m /= 10;
            }
            n = sum;
        }
    }
    return n;
}

在我的系统上运行它需要 12.9 秒。所以它

但是如果我们更进一步呢?我们可以使用一个包含 100 个元素的表格,而不是仅仅使用一个包含 10 个元素的表格。喜欢:

int mul_tab_100[100] = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 1, 2, 5, 10, 17, 26, 37, 50, 65, 82, 4, 5, 8, 13, 20, 29, 40, 53, 68, 85, 9, 10, 13, 18, 25, 34, 45, 58, 73, 90, 16, 17, 20, 25, 32, 41, 52, 65, 80, 97, 25, 26, 29, 34, 41, 50, 61, 74, 89, 106, 36, 37, 40, 45, 52, 61, 72, 85, 100, 117, 49, 50, 53, 58, 65, 74, 85, 98, 113, 130, 64, 65, 68, 73, 80, 89, 100, 113, 128, 145, 81, 82, 85, 90, 97, 106, 117, 130, 145, 162};

int HappyTest()
{
    int n;
    for (int i = 0; i < 210000000; i++) {
        n = i;
        while (n >= 10) {
            int m = n, sum = 0;
            while (m != 0) {
                int t = m >= 100 ? m % 100 : m;  // Notice this change
                sum += mul_tab_100[t];
                m /= 100;                        // Notice this change
            }
            n = sum;
        }
    }
    return n;
}

在我的系统上运行它需要 6.9 秒。所以性能得到了提升

更进一步(即 1000 个元素)只需 5.3 秒 - 又是一个改进

是的,您可以提高运行时性能。

【讨论】:

    【解决方案2】:

    是的,还有提高效率的空间,但我不确定代码实际上做了什么,因为它不会寻找带有快乐数字的重复循环。然而:

    • 去掉不必要的int m

    • 您避免了值 m % 10 模数 但没有 m / 10 除法。最后一位数字可以在循环外计算

    • 使用缓存:您将在循环内反复检查相同的值(见下文)。

    修改后的代码,虽然由于缺少大括号和注释代码而不清楚

    void HappyTest(void)
    {
        for (int i = 0; i < 2100000000; i++) {
            int n = i;
            while (n >= 10) {
                int sum = 0;
                while (n >= 10) {       // while(m != 0)
                    int t = n % 10;
                    n /= 10;
                    sum += t * t;
                }
                sum += n * n;           // final digit moved out of loop
                n = sum;
            }
            if(n == 1 || n == 7) {
                printf("%d\n", i);
            }
        }
    }
    


    回到缓存的想法。请注意,在第一次解析 10 位数字时,其数字的平方和不能超过 9 * 9 * 10 = 810(实际上更小,对于 32 位 int),这会反馈到第二次解析中。所以第一个810 数字可以区别对待——它们的结果存储在一个数组中。剩下的数字只需要对数字进行一次解析,然后就可以查找结果了。
    #define LIMIT       2100000000
    #define CACHE_SZ    810
    
    void HappyTest(void)
    {
        char cached[CACHE_SZ] = { 0 };
    
        // the first part also sets up the cache
        for (int i = 0; i < CACHE_SZ; i++) {
            int n = i;
            while (n >= 10) {
                int sum = 0;
                while (n >= 10) {       // while(m != 0)
                    int t = n % 10;
                    n /= 10;
                    sum += t * t;
                }
                sum += n * n;           // final digit moved out of loop
                n = sum;
            }
            if(n == 1 || n == 7) {
                //printf("%d\n", i);
                cached[i] = 1;
                results++;
            }
        }
    
        // the second part continues more simply
        for (int i = CACHE_SZ; i < LIMIT; i++) {
            int n = i;
            int sum = 0;
            // only one parse of the number
            while (n >= 10) { 
                int t = n % 10;
                n /= 10;
                sum += t * t;
            }
            sum += n * n;
            // then look it up
            if(cached[sum]) {
                //printf("%d\n", i);
                results++;
            }
        }
    }
    

    这大约是您的函数所用时间的 1/3(都没有printf)。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-13
    • 1970-01-01
    • 2012-10-24
    • 1970-01-01
    • 2021-09-09
    相关资源
    最近更新 更多