【问题标题】:Possible explanation for faster execution of two programs in C++ (with Python comparison)?在 C++ 中更快执行两个程序的可能解释(与 Python 比较)?
【发布时间】:2019-08-21 21:51:19
【问题描述】:

更新:C++ 程序(如下所示)在编译时没有附加标志,即g++ program.cpp。然而,提高优化级别并不能改变蛮力比记忆技术运行得更快的事实(0.1 秒 VS 1 秒在我的机器上)。

上下文

我尝试计算Collatz sequence 最长的数字(

我的问题是:蛮力执行速度比 C++ 中所谓的优化(记忆)版本更快的原因可能是什么?

在我的机器(Macbook Air)上的比较之下;时间在 cmets 程序代码的开头。

C++(蛮力)

/**
 * runs in 1 second
 */

#include <iostream>
#include <vector>

unsigned long long nextSequence(unsigned long long n)
{
  if (n % 2 == 0)
    return n / 2;
  else
  {
    return 3 * n + 1;
  }
}

int main()
{
  int max_counter = 0;
  unsigned long long result;
  for (size_t i = 1; i < 1000000; i++)
  {
    int counter = 1;
    unsigned long long n = i;
    while (n != 1)
    {
      n = nextSequence(n);
      counter++;
    }
    if (counter > max_counter)
    {
      max_counter = counter;
      result = i;
    }
  }

  std::cout << result << " has " << max_counter << " sequences." << std::endl;

  return 0;
}

C++(记忆)

/**
 * runs in 2-3 seconds 
 */

#include <iostream>
#include <unordered_map>

int countSequence(uint64_t n, std::unordered_map<uint64_t, uint64_t> &cache)
{
  if (cache.count(n) == 1)
    return cache[n];

  if (n % 2 == 0)
    cache[n] = 1 + countSequence(n / 2, cache);
  else
    cache[n] = 2 + countSequence((3 * n + 1) / 2, cache);

  return cache[n];
}

int main()
{
  uint64_t max_counter = 0;
  uint64_t result;
  std::unordered_map<uint64_t, uint64_t> cache;
  cache[1] = 1;
  for (uint64_t i = 500000; i < 1000000; i++)
  {
    if (countSequence(i, cache) > max_counter)
    {
      max_counter = countSequence(i, cache);
      result = i;
    }
  }

  std::cout << result << std::endl;

  return 0;
}

在 Python 中,记忆技术确实运行得更快。

Python(记忆)

# runs in 1.5 seconds

def countChain(n):
    if n in values:
        return values[n]
    if n % 2 == 0:
        values[n] = 1 + countChain(n / 2)
    else:
        values[n] = 2 + countChain((3 * n + 1) / 2)
    return values[n]


values = {1: 1}
longest_chain = 0
answer = -1

for number in range(500000, 1000000):
    if countChain(number) > longest_chain:
        longest_chain = countChain(number)
        answer = number

print(answer)

Python(蛮力)

# runs in 30 seconds


def countChain(n):
    if n == 1:
        return 1
    if n % 2 == 0:
        return 1 + countChain(n / 2)
    return 2 + countChain((3 * n + 1) / 2)


longest_chain = 0
answer = -1

for number in range(1, 1000000):
    temp = countChain(number)
    if temp > longest_chain:
        longest_chain = temp
        answer = number

print(answer)

【问题讨论】:

  • c++ 版本的所有优化器设置都打开了吗?
  • 不,我不这么认为。刚刚使用g++ program.cpp 编译 - 同时我会尝试设置最高优化级别。
  • @Ely -- 所有关于 C++ 程序与语言 X 相比的速度或为什么一个版本的程序运行速度比另一个版本快的所有问题都应附有用于编译 C++ 应用程序的优化设置.否则显示的所有时间信息都变得毫无意义。
  • unordered_map 执行大量动态内存分配,与基于 CPU 的原始处理相比,这会带来重大损失。
  • @Ely 另请注意,您可以使用gprof 来分析您的代码并检测实际的瓶颈。

标签: python c++ algorithm performance execution-time


【解决方案1】:

我了解您的问题是关于两个 C++ 变体之间的区别,而不是关于编译的 C++ 和解释的 python 之间的区别。果断地回答它需要在打开优化的情况下编译代码并分析其执行情况。并清楚编译器目标是 64 位还是 32 位。

但是考虑到两个版本的 C++ 代码之间的数量级,快速检查已经表明您的记忆消耗的资源多于获得的资源。

这里的一个重要性能瓶颈是无序映射的内存管理。 unordered_mapbuckets of items 一起使用。映射会在必要时调整存储桶的数量,但这需要分配内存(并且可能会移动内存块,具体取决于存储桶的实现方式)。

现在,如果你在缓存初始化之后,在显示结果之前添加以下语句,你会看到number of buckets allocated发生了巨大的变化:

std::cout << "Bucket count: "<<cache.bucket_count()<<"/"<<cache.max_bucket_count()<<std::endl; 

为了避免与此相关的开销,您可以在构造时预先分配桶的数量:

std::unordered_map<uint64_t, uint64_t> cache(3000000);

在 ideone 上进行小型非正式测试可以节省近 50% 的性能。

但是……在unordered_map 中存储和查找对象需要计算由大量算术运算组成的哈希码。所以我猜这些操作比蛮力计算更重。

【讨论】:

  • 谢谢。我觉得你的回答很好。我按照您的建议按照您的建议构建了带有桶数的无序地图。我用g++ -O3 program.cpp(在评论中建议)编译了两个版本,并且在我的机器上都以0.5秒的相同顺序运行。我也相信哈希码算法比你说的蛮力更重。
  • @Ely 感谢您提供这个非常有趣的实验证据确认!
【解决方案2】:

主内存访问大大比计算慢,以至于当需要关心时,您应该将任何超过极少数(cpu-model-dependent)meg 的内容视为从 I /O 或网络设备。

与整数操作相比,即使从 L1 提取也很昂贵。

很久很久以前,这不是真的。几十年来,计算和内存访问至少处于同一水平,因为晶体管预算中根本没有足够的空间来制造足够大的高速缓存来支付费用。

所以人们计算了 CPU 操作,只是假设内存或多或少可以跟上。

如今,它只是……不能。 CPU 缓存未命中的惩罚是 数百 个整数操作,并且您的百万 16 字节条目哈希映射几乎可以保证不仅会破坏 cpu 的内存缓存,还会破坏 TLB,这需要延迟惩罚从痛苦变为毁灭性。

【讨论】:

    猜你喜欢
    • 2016-09-04
    • 2011-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-09
    相关资源
    最近更新 更多