C++似乎比两个Python Ruby for Project Euler都慢得多

C++ appears to be significantly slower than both Python Ruby for Project Euler

本文关键字:Project for Ruby Euler Python 两个 C++      更新时间:2023-10-16

对于Project Euler的以下问题,我有三个解决方案。

如果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();

Python 2:

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()

Ruby:

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和Ruby慢。我意识到,对于每种语言,我可能拥有最有效的整体解决方案,但使用相同类型的操作仍然显示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

您的代码还有另一个严重的问题——比mapunordered_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毫秒。

随着这种变化,mapunordered_map之间的差异也缩小了。使用map的代码仍然可以在20-30毫秒内运行。使用unordered_map的代码可能平均只快一点点,但我的系统时钟只有10ms的粒度,所以我真的必须用更多的数据来测试。

作为参考,以下是我运行时的代码(注意,我对代码进行了一些其他的一般清理,但其他任何事情都不会对速度产生任何重大影响):

#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可以更快。

一旦你纠正了这个问题,mapunordered_map之间的差异(至少大部分)就会消失。

您声称正在计时

查找生成时间是构建一个包含1500个元素的哈希表所需的时间,其中键是一个完美的平方,值是它们各自的根。

Python和Ruby解决方案也是如此,但在C++示例中,您正在构建一个std::map<int, int>。这不是一个哈希表,而是一个红黑树。插入和查找是O(lg N),而不是O(1)

为了得到一个公平的比较,您需要使用std::unordered_map<int, int>作为您的类型。那是一张真正的哈希表。