簡體   English   中英

Python和C ++之間不同尋常的速度差異

[英]Unusual Speed Difference between Python and C++

我最近寫了一個簡短的算法來計算python中的快樂數字 該程序允許您選擇一個上限,它將確定它下面的所有快樂數字。 為了進行速度比較,我決定將我所知道的算法從python直接翻譯成c ++。

令人驚訝的是,c ++版本的運行速度明顯慢於python版本。 在發現前10,000個滿意數字的執行時間之間進行准確的速度測試表明python程序平均在0.59秒內運行,而c ++版本平均在8.5秒內運行。

我將這個速度差異歸結為我必須在c ++版本中編寫部分計算的輔助函數(例如,確定元素是否在列表/數組/向量中),這些函數已經內置到python語言中。

首先,這是否是這種荒謬的速度差異的真正原因,其次,如何更改c ++版本以比python版本更快地執行(在我看來應該如此)。

帶有速度測試的兩段代碼在這里: Python VersionC ++ Version 謝謝您的幫助。

#include <iostream>
#include <vector>
#include <string>
#include <ctime>
#include <windows.h>

using namespace std;

bool inVector(int inQuestion, vector<int> known);
int sum(vector<int> given);
int pow(int given, int power);
void calcMain(int upperBound);

int main()
{
    while(true)
    {
        int upperBound;
        cout << "Pick an upper bound: ";
        cin >> upperBound;
        long start, end;
        start = GetTickCount();
        calcMain(upperBound);
        end = GetTickCount();
        double seconds = (double)(end-start) / 1000.0;
        cout << seconds << " seconds." << endl << endl;
    }
    return 0;
}

void calcMain(int upperBound)
{
    vector<int> known;
    for(int i = 0; i <= upperBound; i++)
    {
        bool next = false;
        int current = i;
        vector<int> history;
        while(!next)
        {
            char* buffer = new char[10];
            itoa(current, buffer, 10);
            string digits = buffer;
            delete buffer;
            vector<int> squares;
            for(int j = 0; j < digits.size(); j++)
            {
                char charDigit = digits[j];
                int digit = atoi(&charDigit);
                int square = pow(digit, 2);
                squares.push_back(square);
            }
            int squaresum = sum(squares);
            current = squaresum;
            if(inVector(current, history))
            {
                next = true;
                if(current == 1)
                {
                    known.push_back(i);
                    //cout << i << "\t";
                }
            }
            history.push_back(current);
        }
    }
    //cout << "\n\n";
}

bool inVector(int inQuestion, vector<int> known)
{
    for(vector<int>::iterator it = known.begin(); it != known.end(); it++)
        if(*it == inQuestion)
            return true;
    return false;
}

int sum(vector<int> given)
{
    int sum = 0;
    for(vector<int>::iterator it = given.begin(); it != given.end(); it++)
        sum += *it;
    return sum;
}

int pow(int given, int power)
{
    int original = given;
    int current = given;
    for(int i = 0; i < power-1; i++)
        current *= original;
    return current;
}

#!/usr/bin/env python

import timeit

upperBound = 0

def calcMain():
    known = []
    for i in range(0,upperBound+1):
        next = False
        current = i
        history = []
        while not next:
            digits = str(current)
            squares = [pow(int(digit), 2) for digit in digits]
            squaresum = sum(squares)
            current = squaresum
            if current in history:
                next = True
                if current == 1:
                    known.append(i)
                    ##print i, "\t",
            history.append(current)
    ##print "\nend"

while True:    
    upperBound = input("Pick an upper bound: ")
    result = timeit.Timer(calcMain).timeit(1)
    print result, "seconds.\n"

對於100000個元素,Python代碼需要6.9秒,而C ++最初需要37秒。

我對您的代碼進行了一些基本的優化,並設法使C ++代碼比Python實現快100倍。 它現在在0.06秒內完成100000個元素。 這比原始C ++代碼快617倍。

最重要的是在發布模式下進行編譯,並進行所有優化。 在調試模式下,此代碼實際上要慢一個數量級。

接下來,我將解釋我所做的優化。

  • 將所有向量聲明移到循環之外; 用clear()操作替換它們,這比調用構造函數要快得多。
  • 用乘法:value *值替換對pow(value,2)的調用。
  • 我沒有使用方形向量和調用求和,而是僅使用整數就地求值。
  • 避免所有字符串操作,與整數操作相比,這些操作非常慢。 例如,可以通過重復除以10並獲取結果值的模數10來計算每個數字的平方,而不是將該值轉換為字符串,然后將每個字符轉換回int。
  • 避免所有矢量副本,首先通過傳遞引用替換傳遞值,最后完全消除輔助函數。
  • 消除了一些臨時變量。
  • 可能很多細節我都忘記了。 比較你的代碼和我的並排,看看我做了什么。

通過使用預先分配的數組而不是向量,可以更好地優化代碼,但這將是更多的工作,我將把它作為練習留給讀者。 :P

這是優化的代碼:

#include <iostream>
#include <vector>
#include <string>
#include <ctime>
#include <algorithm>
#include <windows.h>

using namespace std;

void calcMain(int upperBound, vector<int>& known);

int main()
{
    while(true)
    {
        vector<int> results;
        int upperBound;
        cout << "Pick an upper bound: ";
        cin >> upperBound;
        long start, end;
        start = GetTickCount();
        calcMain(upperBound, results);
        end = GetTickCount();
        for (size_t i = 0; i < results.size(); ++i) {
            cout << results[i] << ", ";
        }
        cout << endl;
        double seconds = (double)(end-start) / 1000.0;
        cout << seconds << " seconds." << endl << endl;
    }
    return 0;
}

void calcMain(int upperBound, vector<int>& known)
{
    vector<int> history;
    for(int i = 0; i <= upperBound; i++)
    {
        int current = i;
        history.clear();
        while(true)
        {
                int temp = current;
                int sum = 0;
                while (temp > 0) {
                    sum += (temp % 10) * (temp % 10);
                    temp /= 10;
                }
                current = sum;
                if(find(history.begin(), history.end(), current) != history.end())
                {
                        if(current == 1)
                        {
                                known.push_back(i);
                        }
                        break;
                }
                history.push_back(current);
        }
    }
}

有一個新的,速度更快的版本作為單獨的答案 ,所以這個答案已被棄用。


我重寫了你的算法,只要它找到快樂或不快樂的數字就會緩存。 我也試圖盡可能地使它成為pythonic,例如通過創建單獨的函數digits()happy() 很抱歉使用Python 3,但我也可以從中展示一些有用的東西。

這個版本要快得多 它的運行速度為1.7 ,比你需要18s的原始程序快10倍 (好吧,我的MacBook很老很慢:))

#!/usr/bin/env python3

from timeit import Timer
from itertools import count

print_numbers = False
upperBound = 10**5  # Default value, can be overidden by user.


def digits(x:'nonnegative number') -> "yields number's digits":
    if not (x >= 0): raise ValueError('Number should be nonnegative')
    while x:
        yield x % 10
        x //= 10


def happy(number, known = {1}, happies = {1}) -> 'True/None':
    '''This function tells if the number is happy or not, caching results.

    It uses two static variables, parameters known and happies; the
    first one contains known happy and unhappy numbers; the second 
    contains only happy ones.

    If you want, you can pass your own known and happies arguments. If
    you do, you should keep the assumption commented out on the 1 line.

    '''

#        assert 1 in known and happies <= known  # <= is expensive

    if number in known:
        return number in happies

    history = set()
    while True:
        history.add(number)
        number = sum(x**2 for x in digits(number))
        if number in known or number in history:
            break

    known.update(history)
    if number in happies:
        happies.update(history)
        return True


def calcMain():
    happies = {x for x in range(upperBound) if happy(x) }
    if print_numbers:
        print(happies)


if __name__ == '__main__':
    upperBound = eval(
            input("Pick an upper bound [default {0}]: "
                    .format(upperBound)).strip()
            or repr(upperBound))
    result = Timer(calcMain).timeit(1)
    print ('This computation took {0} seconds'.format(result))

看起來你正在按值傳遞向量到其他函數。 這將是一個顯着的減速,因為程序實際上會在向量傳遞給函數之前制作完整的矢量副本。 要解決此問題,請將常量引用傳遞給向量而不是副本。 所以代替:

int sum(vector<int> given)

使用:

int sum(const vector<int>& given)

執行此操作時,您將無法再使用vector :: iterator,因為它不是常量。 你需要用vector :: const_iterator替換它。

您也可以傳入非常量引用,但在這種情況下,您根本不需要修改參數。

我可以看到你有很多不必要的堆分配

例如:

while(!next)
        {
            char* buffer = new char[10];

這看起來不是很優化。 因此,您可能希望預先分配數組並在循環中使用它。 這是一種易於發現和完成的基本優化技術。 它也可能變得一團糟,所以要小心。

你也在使用atoi()函數,我真的不知道它是否真的被優化了。 也許做模數10並得到數字可能會更好(你必須衡量你,我沒有測試這個)。

您進行線性搜索(inVector)的事實可能不好。 用std :: set替換矢量數據結構可能會加快速度。 hash_set也可以做到這一點。

但我認為最糟糕的問題是字符串和這個循環中堆上的東西的分配。 這看起來不太好。 我會先嘗試那些地方。

這是我的第二個答案; 它會緩存值為平方和的值<= 10**6

        happy_list[sq_list[x%happy_base] + sq_list[x//happy_base]]

那是,

  • 號碼分為3位+ 3位數
  • 預先計算的表用於獲得兩個部分的平方和
  • 這兩個結果被添加
  • 咨詢預先計算的表以獲得數字的幸福:

我不認為Python版本可以比這更快(好吧,如果你扔掉舊版本,那就是try:開銷,它快10%)。

我認為這是一個很好的問題 ,它表明,事實上,

  • 必須快速的事情應該用C語寫
  • 但是,通常你不需要快速的東西(即使你需要程序運行一天,它會少於程序員優化它的總時間)
  • 用Python編寫程序更容易,更快捷
  • 但對於某些問題,尤其是計算問題,C ++解決方案(如上所述)實際上比嘗試優化Python程序更具可讀性和美觀性。

好的,在這里(第2版現在......):

#!/usr/bin/env python3
'''Provides slower and faster versions of a function to compute happy numbers.

slow_happy() implements the algorithm as in the definition of happy
numbers (but also caches the results).

happy() uses the precomputed lists of sums of squares and happy numbers
to return result in just 3 list lookups and 3 arithmetic operations for
numbers less than 10**6; it falls back to slow_happy() for big numbers.

Utilities: digits() generator, my_timeit() context manager.

'''


from time import time  # For my_timeit.
from random import randint # For example with random number.

upperBound = 10**5  # Default value, can be overridden by user.


class my_timeit:
    '''Very simple timing context manager.'''

    def __init__(self, message):
        self.message = message
        self.start = time()

    def __enter__(self):
        return self

    def __exit__(self, *data):
        print(self.message.format(time() - self.start))


def digits(x:'nonnegative number') -> "yields number's digits":
    if not (x >= 0): raise ValueError('Number should be nonnegative')
    while x:
        yield x % 10
        x //= 10


def slow_happy(number, known = {1}, happies = {1}) -> 'True/None':
    '''Tell if the number is happy or not, caching results.

    It uses two static variables, parameters known and happies; the
    first one contains known happy and unhappy numbers; the second 
    contains only happy ones.

    If you want, you can pass your own known and happies arguments. If
    you do, you should keep the assumption commented out on the 1 line.

    '''
    # This is commented out because <= is expensive.
    # assert {1} <= happies <= known 

    if number in known:
        return number in happies

    history = set()
    while True:
        history.add(number)
        number = sum(x**2 for x in digits(number))
        if number in known or number in history:
            break

    known.update(history)
    if number in happies:
        happies.update(history)
        return True


# This will define new happy() to be much faster ------------------------.

with my_timeit('Preparation time was {0} seconds.\n'):

    LogAbsoluteUpperBound = 6 # The maximum possible number is 10**this.
    happy_list = [slow_happy(x)
                  for x in range(81*LogAbsoluteUpperBound + 1)]
    happy_base = 10**((LogAbsoluteUpperBound + 1)//2)
    sq_list = [sum(d**2 for d in digits(x))
               for x in range(happy_base + 1)]

    def happy(x):
        '''Tell if the number is happy, optimized for smaller numbers.

        This function works fast for numbers <= 10**LogAbsoluteUpperBound.

        '''
        try:
            return happy_list[sq_list[x%happy_base] + sq_list[x//happy_base]]
        except IndexError:
            return slow_happy(x)

# End of happy()'s redefinition -----------------------------------------.


def calcMain(print_numbers, upper_bound):
    happies = [x for x in range(upper_bound + 1) if happy(x)]
    if print_numbers:
        print(happies)


if __name__ == '__main__':
    while True:

        upperBound = eval(input(
            "Pick an upper bound [{0} default, 0 ends, negative number prints]: "
            .format(upperBound)).strip() or repr(upperBound))
        if not upperBound:
            break

        with my_timeit('This computation took {0} seconds.'):
            calcMain(upperBound < 0, abs(upperBound))

        single = 0
        while not happy(single):
            single = randint(1, 10**12)
        print('FYI, {0} is {1}.\n'.format(single,
                    'happy' if happy(single) else 'unhappy')) 

    print('Nice to see you, goodbye!')

好吧,我也給了它一次性。 不過,我沒有測試甚至編譯。

數值計划的一般規則:

  • 切勿將數字作為文本處理。 這就是使較少的語言比Python慢​​的原因所以如果你在C語言中這樣做,程序將比Python慢​​。

  • 如果可以避免使用數據結構,請不要使用它們。 您正在構建一個數組只是為了添加數字。 更好地保持運行總量。

  • 保持STL引用的副本打開,以便您可以使用它而不是編寫自己的函數。


void calcMain(int upperBound)
{
    vector<int> known;
    for(int i = 0; i <= upperBound; i++)
    {
        int current = i;
        vector<int> history;
        do
        {
            squaresum = 0
            for ( ; current; current /= 10 )
            {
                int digit = current % 10;
                squaresum += digit * digit;
            }
            current = squaresum;
            history.push_back(current);
        } while ( ! count(history.begin(), history.end() - 1, current) );

        if(current == 1)
        {
            known.push_back(i);
            //cout << i << "\t";
        }

    }
    //cout << "\n\n";
}

通過查看我能夠多快找到這些數字來獲得關於這個問題的更多關閉,我寫了一個Dr_Asik算法的多線程C ++實現。 關於這個實現是多線程的事實,有兩件事是很重要的。

  1. 更多線程不一定會帶來更好的執行時間,根據您想要計算的數字量,每種情況都有一個愉快的媒介。

  2. 如果比較使用一個線程運行的此版本與原始版本之間的時間,那么可能導致時間差異的唯一因素是啟動線程的開銷和可變系統性能問題。 否則,算法是相同的。

這個實現的代碼(算法的所有功勞歸到Dr_Asik)都在這里 另外,我寫了一些速度測試,每次測試都要經過雙重檢查,以幫助備份這3點。

計算前100,000,000個快樂數字:

原始 - 39.061 / 39.000(Dr_Asik的原始實施)
1個主題 - 39.000 / 39.079
2個主題 - 19.750 / 19.890
10個主題 - 11.872 / 11.888
30個主題 - 10.764 / 10.827
50個主題 - 10.624 / 10.561 < -
100個主題 - 11.060 / 11.216
500個主題 - 13.385 / 12.527

從這些結果來看,我們的快樂媒體看起來大約有50個線程,正負十個左右。

其他優化 :通過使用數組和使用循環索引直接訪問而不是在向量中搜索,並通過緩存先前的總和,以下代碼(受Asik博士的答案啟發,但可能根本沒有優化)運行速度比原始C ++快2445倍代碼,比Python代碼快約400倍。

#include <iostream>
#include <windows.h>
#include <vector>

void calcMain(int upperBound, std::vector<int>& known)
{
    int tempDigitCounter = upperBound;
    int numDigits = 0;
    while (tempDigitCounter > 0)
    {
        numDigits++;
        tempDigitCounter /= 10;
    }
    int maxSlots = numDigits * 9 * 9;
    int* history = new int[maxSlots + 1];

    int* cache = new int[upperBound+1];
    for (int jj = 0; jj <= upperBound; jj++)
    {
        cache[jj] = 0;
    }

    int current, sum, temp;
    for(int i = 0; i <= upperBound; i++)
    {
        current = i;
        while(true)
        {
            sum = 0;
            temp = current;

            bool inRange = temp <= upperBound;
            if (inRange)
            {
                int cached = cache[temp];
                if (cached)
                {
                    sum = cached;
                }
            }

            if (sum == 0)
            {
                while (temp > 0)
                {
                    int tempMod = temp % 10;
                    sum += tempMod * tempMod;
                    temp /= 10;
                }
                if (inRange)
                {
                    cache[current] = sum;
                }
            }
            current = sum;
            if(history[current] == i)
            {
                if(current == 1)
                {
                    known.push_back(i);
                }
                break;
            }
            history[current] = i;
        }
    }
}

int main()
{
    while(true)
    {
        int upperBound;
        std::vector<int> known;
        std::cout << "Pick an upper bound: ";
        std::cin >> upperBound;
        long start, end;
        start = GetTickCount();
        calcMain(upperBound, known);
        end = GetTickCount();
        for (size_t i = 0; i < known.size(); ++i) {
            std::cout << known[i] << ", ";
        }               
        double seconds = (double)(end-start) / 1000.0;
        std::cout << std::endl << seconds << " seconds." << std::endl << std::endl;
    }
    return 0;
}

在無聊的時候偶然發現了這個頁面,我以為我會用js打高爾夫球。 算法是我自己的,除了我自己的計算之外,我還沒有徹底檢查過它(所以它可能是錯誤的)。 它計算出第一個1e7個滿意的數字並將它們存儲在h中。 如果要更改它,請同時更改7。

m=1e7,C=7*81,h=[1],t=true,U=[,,,,t],n=w=2;
while(n<m){
z=w,s=0;while(z)y=z%10,s+=y*y,z=0|z/10;w=s;
if(U[w]){if(n<C)U[n]=t;w=++n;}else if(w<n)h.push(n),w=++n;}

這將在控制台或瀏覽器中為您打印前1000個項目:

o=h.slice(0,m>1e3?1e3:m);
(!this.document?print(o):document.load=document.write(o.join('\n')));

功能部分有155個字符,它看起來和Asik博士在firefox或v8上提供的速度一樣快(運行時d8 happygolf.js或js -a時,系統上原始python程序的350-400倍速度) j -p happygolf.js in spidermonkey)。
我將敬畏分析技能,任何人都可以弄清楚為什么這個算法做得很好而沒有引用更長,評論,fortran版本。

我對它的速度感到好奇,所以我學會了fortran以獲得相同算法的比較,如果有任何明顯的新手錯誤,請善待它,這是我的第一個fortran程序。 http://pastebin.com/q9WFaP5C這是靜態內存,所以為了公平對待其他人,它是在一個自編譯的shell腳本中,如果你沒有gcc / bash / etc剝離預處理器和bash的東西在頂部,手動設置宏並將其編譯為fortran95。

即使你包括編譯時間,它也會勝過大多數其他人。 如果不這樣做,它的速度大約是原始python版本的3000-3500倍(並且擴展速度> C ++ *的40,000倍,盡管我沒有運行任何C ++程序)。

令人驚訝的是,我在fortran版本中嘗試過的許多優化(包括一些類似於循環展開,由於效果和可讀性小而我從粘貼版本中刪除)對js版本是不利的。 這個練習表明,現代的跟蹤編譯器非常好(在精心優化的靜態內存fortran的7-10倍之內),如果你走開他們的路並且不嘗試任何棘手的東西。 走開他們的路,試圖做一些棘手的事情最后,這是一個更好,更遞歸的js版本。

// to s, then integer divides x by 10.
// Repeats until x is 0.
function sumsq(x) {
  var y,s=0;
  while(x) {
    y = x % 10; 
    s += y * y;
    x = 0| x / 10; 
  }
  return s;
}
// A boolean cache for happy().
// The terminating happy number and an unhappy number in
// the terminating sequence.
var H=[];
H[1] = true;
H[4] = false;
// Test if a number is happy.
// First check the cache, if that's empty
// Perform one round of sumsq, then check the cache
// For that. If that's empty, recurse.
function happy(x) {
  // If it already exists.
  if(H[x] !== undefined) {
    // Return whatever is already in cache.
    return H[x];
  } else {
    // Else calc sumsq, set and  return cached val, or if undefined, recurse.
    var w = sumsq(x);
    return (H[x] = H[w] !== undefined? H[w]: happy(w));
  }
}
//Main program loop.
var i, hN = []; 
for(i = 1; i < 1e7; i++) {
  if(happy(i)) { hN.push(i); }
}

令人驚訝的是,即使它是相當高的級別,它幾乎與spidermonkey中的命令式算法(優化開啟)和v8中的close(1.2倍長)完全一樣。

我想這個故事的道德,如果重要的話,花點時間考慮一下你的算法。 此外,高級語言已經有很多開銷(有時候還有自己的技巧來減少它),所以有時候做一些更直接的或者利用它們的高級功能也同樣快。 微優化並不總是有幫助。

*除非我的python安裝異常緩慢,否則直接時間有點無意義,因為這是第一代eee。 時代是:
forts版12s,沒有輸出,1e8個滿意的數字。
forts版本為40s,管道輸出通過gzip到磁盤。
兩個js版本都是8-12s。 1e7個滿意的數字,沒有完全優化的輸出10個版本1e7的10-100s,有更少/沒有優化(取決於沒有優化的定義,100s是eval())沒有輸出

我有興趣在真正的計算機上查看這些程序的時間。

我不是C ++優化方面的專家,但我相信速度差異可能是因為Python列表在開始時預先分配了更多空間,而C ++向量必須重新分配並且可能在每次增長時復制。

至於GMan關於find的評論,我相信Python“in”運算符也是線性搜索並且速度相同。

編輯

另外我只是注意到你推出了自己的戰隊功能。 沒有必要這樣做,stdlib可能更快。

這是另一種依賴於記憶已經探索過的數字的方法。 我得到一個因子x4-5,這對於DrAsik的1000和1000000的代碼是奇怪的穩定,我期望緩存在我們探索的數字越多時效率越高。 否則,已經應用了相同類型的經典優化。 順便說一句,如果編譯器接受NRVO (/ RNVO?我永遠不記得確切的術語)或右值引用,我們就不需要將向量作為out參數傳遞。

注意:微觀優化仍然是可能的恕我直言,而且緩存是天真的,因為它分配的內存比真正需要的多得多。

enum Status {
    never_seen,
    being_explored,
    happy,
    unhappy
};

char const* toString[] = { "never_seen", "being_explored", "happy", "unhappy" };


inline size_t sum_squares(size_t i) {
    size_t s = 0;
    while (i) {
        const size_t digit = i%10;
        s += digit * digit;
        i /= 10;
    }
    return s ;
}

struct Cache {
    Cache(size_t dim) : m_cache(dim, never_seen) {}
    void set(size_t n, Status status) {
        if (m_cache.size() <= n) {
            m_cache.resize(n+1, never_seen);
        }
        m_cache[n] = status;
        // std::cout << "(c[" << n << "]<-"<<toString[status] << ")";
    }
    Status operator[](size_t n) const {
        if (m_cache.size() <= n) {
            return never_seen;
        } else {
            return m_cache[n];
        }
    }

private:
    std::vector<Status> m_cache;
};

void search_happy_lh(size_t upper_bound, std::vector<size_t> & happy_numbers)
{
    happy_numbers.clear();
    happy_numbers.reserve(upper_bound); // it doesn't improve much the performances

    Cache cache(upper_bound+1);
    std::vector<size_t> current_stack;

    cache.set(1,happy);
    happy_numbers.push_back(1);
    for (size_t i = 2; i<=upper_bound ; ++i) {
        // std::cout << "\r" << i << std::flush;
        current_stack.clear();
        size_t s= i;
        while ( s != 1 && cache[s]==never_seen)
        {
            current_stack.push_back(s);
            cache.set(s, being_explored);
            s = sum_squares(s);
            // std::cout << " - " << s << std::flush;
        }
        const Status update_with = (cache[s]==being_explored ||cache[s]==unhappy) ? unhappy : happy;
        // std::cout << " => " << s << ":" << toString[update_with] << std::endl;
        for (size_t j=0; j!=current_stack.size(); ++j) {
            cache.set(current_stack[j], update_with);
        }
        if (cache[i] == happy) {
            happy_numbers.push_back(i);
        }
    }
}

這里有一些值得思考的問題:如果選擇運行1979算法來查找2009計算機中的素數或1979年計算機上的2009算法,您會選擇哪種?

古代硬件上的新算法將是一個巨大的優勢選擇。 看看你的“助手”功能。

有很多可能的優化:

(1)使用const引用

bool inVector(int inQuestion, const vector<int>& known)
{
    for(vector<int>::const_iterator it = known.begin(); it != known.end(); ++it)
        if(*it == inQuestion)
            return true;
    return false;
}

int sum(const vector<int>& given)
{
    int sum = 0;
    for(vector<int>::const_iterator it = given.begin(); it != given.end(); ++it)
        sum += *it;
    return sum;
}

(2)使用倒計時循環

int pow(int given, int power)
{
    int current = 1;
    while(power--)
        current *= given;
    return current;
}

或者,正如其他人所說,使用標准庫代碼。

(3)不要在不需要的地方分配緩沖區

        vector<int> squares;
        for (int temp = current; temp != 0; temp /= 10)
        {
            squares.push_back(pow(temp % 10, 2));
        }

通過與PotatoSwatter類似的優化,我有10000個數字的時間從1.063秒下降到0.062秒(除了我用原始的標准sprintf替換了itoa)。

使用所有內存優化(不按值傳遞容器 - 在C ++中,您必須明確決定是否需要副本或引用;移動從內部循環中分配內存的操作;如果已在char緩沖區中使用該數字,將它復制到std :: string等是什么意思)我把它降到了0.532。

剩下的時間來自使用%10來訪問數字,而不是將數字轉換為字符串。

我想可能會有另一個算法級別優化(你找到一個幸福數字時遇到的數字本身也是幸福的數字?)但我不知道獲得了多少(首先沒有那么多的數字)而且這種優化也不在Python版本中。

順便說一句,通過不使用字符串轉換和列表到方形數字,我將Python版本從0.825秒降低到0.33。

這是一個C#版本:

using System;
using System.Collections.Generic;
using System.Text;

namespace CSharp
{
  class Program
  {
    static void Main (string [] args)
    {
      while (true)
      {
        Console.Write ("Pick an upper bound: ");

        String
          input = Console.ReadLine ();

        uint
          upper_bound;

        if (uint.TryParse (input, out upper_bound))
        {
          DateTime
            start = DateTime.Now;

          CalcHappyNumbers (upper_bound);

          DateTime
            end = DateTime.Now;

          TimeSpan
            span = end - start;

          Console.WriteLine ("Time taken = " + span.TotalSeconds + " seconds.");
        }
        else
        {
          Console.WriteLine ("Error in input, unable to parse '" + input + "'.");
        }
      }
    }

    enum State
    {
      Happy,
      Sad,
      Unknown
    }

    static void CalcHappyNumbers (uint upper_bound)
    {
      SortedDictionary<uint, State>
        happy = new SortedDictionary<uint, State> ();

      SortedDictionary<uint, bool>
        happy_numbers = new SortedDictionary<uint, bool> ();

      happy [1] = State.Happy;
      happy_numbers [1] = true;

      for (uint current = 2 ; current < upper_bound ; ++current)
      {
        FindState (ref happy, ref happy_numbers, current);
      }

      //foreach (KeyValuePair<uint, bool> pair in happy_numbers)
      //{
      //  Console.Write (pair.Key.ToString () + ", ");
      //}

      //Console.WriteLine ("");
    }

    static State FindState (ref SortedDictionary<uint, State> happy, ref SortedDictionary<uint,bool> happy_numbers, uint value)
    {
      State
        current_state;

      if (happy.TryGetValue (value, out current_state))
      {
        if (current_state == State.Unknown)
        {
          happy [value] = State.Sad;
        }
      }
      else
      {
        happy [value] = current_state = State.Unknown;

        uint
          new_value = 0;

        for (uint i = value ; i != 0 ; i /= 10)
        {
          uint
            lsd = i % 10;

          new_value += lsd * lsd;
        }

        if (new_value == 1)
        {
          current_state = State.Happy;
        }
        else
        {
          current_state = FindState (ref happy, ref happy_numbers, new_value);
        }

        if (current_state == State.Happy)
        {
          happy_numbers [value] = true;
        }

        happy [value] = current_state;
      }

      return current_state;
    }
  }
}

我將它與Dr_Asik的C ++代碼進行了比較。 對於100000的上限,C ++版本在大約2.9秒內運行,C#版本在0.35秒內運行。 兩者都是使用Dev Studio 2005使用默認版本構建選項編譯的,並且都是從命令提示符執行的。


#!/usr/bin/env python

import timeit

upperBound = 0

def calcMain():
    known = set()
    for i in xrange(0,upperBound+1):
        next = False
        current = i
        history = set()
        while not next:
            squaresum=0
            while current > 0:
                current, digit = divmod(current, 10)
                squaresum += digit * digit
            current = squaresum
            if current in history:
                next = True
                if current == 1:
                    known.add(i)
            history.add(current)

while True:
    upperBound = input("Pick an upper bound: ")
    result = timeit.Timer(calcMain).timeit(1)
    print result, "seconds.\n"

我對你原來的python代碼示例進行了一些小改動,使得代碼性能提高了16倍。 我做出的改變將100,000箱從大約9.64秒提高到大約3.38秒。

主要的變化是使mod 10和累加器更改在while循環中運行。 我做了一些其他的改動,只用了幾分之幾秒就改善了執行時間。 第一個小改動是將main for循環從范圍列表解析更改為xrange迭代器。 第二個小變化是將set類替換為已知變量和歷史變量的列表類。 我還嘗試了迭代器理解和預先計算方塊,但它們都對效率產生了負面影響。 我似乎運行的是較慢版本的python,或者運行速度較慢的處理器而不是其他一些貢獻者。 我會對其他人將我的python代碼與同一算法的優化C ++版本之一進行時序比較的結果感興趣。 我也嘗試使用python -O和-OO優化,但它們與預期效果相反。

為什么每個人都在c ++版本中使用向量? 查找時間是O(N)。

盡管它不如python集有效,但使用std :: set。 查找時間為O(log(N))。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM