C++ 似乎比 Project Euler 的 Python Ruby 慢得多
Posted
技术标签:
【中文标题】C++ 似乎比 Project Euler 的 Python Ruby 慢得多【英文标题】:C++ appears to be significantly slower than both Python Ruby for Project Euler 【发布时间】:2015-02-08 01:25:54 【问题描述】:我有来自 Project Euler 的以下问题的 3 个解决方案。
如果 p 是一个整数长度的直角三角形的周长 边,a,b,c,对于 p = 120,恰好有三个解。
20,48,52,24,45,51,30,40,50
对于 p ≤ 1000 的哪个值,解的数量是否最大?
我为每种语言提供的三种解决方案如下。
C++:
boost::chrono::steady_clock::time_point start_time = boost::chrono::steady_clock::now();
map<int, int> square_lookup;
for(int i=0; i<= 1500; i++)
square_lookup[i*i] = i ;
auto end_time = boost::chrono::steady_clock::now();
Python2:
start = time.time()
res = range(1, 1501)
squares =
#square_lookups = dict(zip([x*x for x in res], res))
square_lookups =
for x in range(1, 1501):
square_lookups[x*x] = x
end = time.time()
鲁比:
start_time = Time.now
square_lookup =
(1 .. 1500).map |x| x*x.each_with_index do |square, root|
square_lookup[square] = root+1
end
end_time = Time.now
四核 i5 的时序:
> lookup gen time: 0.00141787528992
> Python Result: 840 Time:
> 0.282248973846
>
> Lookup gen time 4640960 nanoseconds
> C++: Result: 840 : Time: 695301578 nanoseconds
>
>
> Lookup gen time 0.000729416
> Ruby: Result: 840 Time: 0.149393345
查找生成时间是构造一个包含 1500 个元素的哈希表所花费的时间,其中键是完美的平方,值是它们各自的根。
即使在这方面,C++ 仍然比 Python 和 RubySLOWER。我意识到对于每种语言,我可能拥有总体上最有效的解决方案,但使用相同类型的操作仍然表明 C++ 非常慢。
重要编辑我将map
更改为使用unordered_map
作为C++ 解决方案,但它仍然较慢!
修改后的 C++ 文件:http://pastebin.com/2YyB6Rfm
lookup gen time: 0.00134301185608
Python Result: 840 Time: 0.280808925629
Lookup gen time 2021697 nanoseconds
C++: Result: 840 : Time: 392731891 nanoseconds
Lookup gen time 0.000729313
Ruby: Result: 840 Time: 0.148183345
【问题讨论】:
不,问题解决了,我在问为什么C++实现比较慢。 既然已经有chronos库,为什么还要使用boost/chrono库? 你为什么评论与我的问题无关的无用的cmets? 你用什么优化设置编译的? @ViktorChynarov:C++ 标准库在没有优化的情况下是出了名的慢。 (就像我编写的大多数 C++ 代码一样。) 【参考方案1】:您的代码还有另一个严重的问题——比map
和unordered_map
严重得多(至少在IMO)。
尤其是你在哪里做的:
int result = square_lookup[(i*i) + (j*j)];
if(result)
int perimeter = i + j + result;
if(perimeter <= 1000)
occurences[perimeter] += 1;
此代码不只是在现有地图中查找值i*i+j*j
。相反,如果映射中不存在键,它会在映射中插入一个节点,其中i*i+j*j
作为键,0
(或者更具体地说,映射的 value_type
的值初始化对象,在这种情况下是int
)进入地图。
在地图中为您不关心的所有值插入节点非常慢。您在这里尝试做的实际上只是检查该值是否已经在地图中。为此,您可以使用如下代码:
auto result = square_lookup.find(i*i + j*j);
if (result!=square_lookup.end())
int perimeter = i + j + result->second;
if (perimeter <= 1000)
++occurences[perimeter];
这使用find
来查找键是否在映射中。然后如果(且仅当)键在映射中,它会查找当前与该键关联的值。
这大大提高了速度 - 使用 VC++ 或 g++ 大约为 20-30 毫秒。
随着这种变化的发生,map
和 unordered_map
之间的差异也缩小了。使用 map
的代码仍然可以在 ~20-30 毫秒内运行。平均而言,使用unordered_map
的代码可能会稍微快一点,但我的系统时钟只有 10 毫秒的粒度,所以我真的必须用更多的数据进行测试才能确定。
作为参考,这是我运行时的代码(请注意,我对代码进行了一些其他常规清理,但其他任何事情都不会对速度产生任何重大影响):
#include <iostream>
#include <unordered_map>
#include <chrono>
#include <iterator>
#include <algorithm>
#include <utility>
#include <map>
using namespace std;
int main()
auto start_time = chrono::steady_clock::now();
map<int, int> square_lookup;
int ctr = 0;
generate_n(inserter(square_lookup, square_lookup.end()),
1500,
[&]() ++ctr; return make_pair(ctr*ctr, ctr); );
auto end_time = chrono::steady_clock::now();
cout << "Lookup gen time "
<< chrono::duration_cast<chrono::milliseconds>(end_time - start_time).count() << "\n";
map<int, int> occurences;
typedef std::pair<int, int> const &map_t;
for (int i = 0; i <= 1000; i++)
for (int j = i; j <= 1000; j++)
auto result = square_lookup.find(i*i + j*j);
if (result != square_lookup.end())
int perimeter = i + j + result->second;
if (perimeter <= 1000)
++occurences[perimeter];
auto it = std::max_element(occurences.begin(), occurences.end(),
[](map_t a, map_t b) return a.second < b.second; );
end_time = chrono::steady_clock::now();
cout << "C++: Result: " << it->first << " : Time: "
<< chrono::duration_cast<chrono::milliseconds>(end_time - start_time).count() << "\n";
总结:在 C++ 中,map
上的 []
运算符将插入一个不存在的项目。这可能很方便,但并不总是您想要的。如果您只想检索一个已经存在的值,那么它不是适合这项工作的工具——.find
可以大大加快速度。
一旦你纠正了这个问题,map
和 unordered_map
之间的区别(至少大部分情况下)就消失了。
【讨论】:
make_pair(ctr, ctr*ctr)
- 不应该是相反的顺序吗?
@T.C.:糟糕——是的,确实应该。谢谢。
@ViktorChynarov:不幸的是,这个答案所指的原始问题中的代码不再可见(没有进入问题的编辑历史记录,然后点击 pastebin 链接)。跨度>
【参考方案2】:
你声称你在计时
查找生成时间是构造一个包含 1500 个元素的哈希表所需的时间,其中键是一个完美的正方形,值是它们各自的根。
对于 Python 和 Ruby 解决方案来说确实如此,但在 C++ 示例中,您正在构建一个 std::map<int, int>
。那是不是哈希表——它是一棵红黑树。插入和查找是O(lg N)
,而不是O(1)
。
为了公平比较,您希望使用std::unordered_map<int, int>
作为您的类型。那是一个真正的哈希表。
【讨论】:
谢谢,我把map的实例改成了unordered_map,但是还是比较慢。 C++:结果:840:时间:392731891 纳秒 你编译优化了吗? (即,您是否将-O2
或-O3
传递给编译器?)此外,为了使用std::unordered_map
获得最佳结果,请在使用前将最大负载因子设置为合理的值。例如,yourmap.max_load_factor(0.75);
我现在同时使用 -O2 和 -O3 进行编译,它让它变得更快了。大约 0.12 秒,将 max_load_factor 更改为 0.75 会有所不同(从 0.15 不变)。以上是关于C++ 似乎比 Project Euler 的 Python Ruby 慢得多的主要内容,如果未能解决你的问题,请参考以下文章
用 Javascript 解决 Project Euler 16