为什么我的计算在C#中比Python快得多

ssd*_*ssd 16 c# python

下面是一个简单的过程编码C#Python(对于你们好奇的过程,它是项目欧拉问题5的解决方案).

我的问题是,C#下面的代码只需要9秒来迭代,而Python代码的完成需要283秒(确切地说,Python 3.4.3上的283秒 - 64位和Python 2.7.9上的329秒--32位).

到目前为止,我已经编写了类似的进程C#,Python并且执行时间差异可比.然而,这一次,经过的时间之间存在极大的差异.

我认为,这种差异的某些部分来自灵活变量类型的python语言(我怀疑,python将变量的某些部分转换为double)但是这仍然很难解释.

我究竟做错了什么?

我的系统:Windows-7 64位,

C# - VS Express 2012(9秒)

Python 3.4.3 64位(283秒)

Python 2.7.9 32位(329秒)

c-sharp代码:

using System;

namespace bug_vcs {
    class Program {
        public static void Main(string[] args) {
            DateTime t0 = DateTime.Now;
            int maxNumber = 20;
            bool found = false;
            long start = maxNumber;
            while (!found) {
                found = true;
                int i = 2;
                while ((i < maxNumber + 1) && found) {
                    if (start % i != 0) {
                        found = false;
                    }
                    i++;
                }
                start++;
            }
            Console.WriteLine("{0:d}", start - 1);
            Console.WriteLine("time elapsed = {0:f} sec.", (DateTime.Now - t0).Seconds);
            Console.ReadLine();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

和python代码:

from datetime import datetime

t0 = datetime.now()
max_number = 20
found = False
start = max_number
while not found:
    found = True
    i = 2
    while ((i < max_number + 1) and found):
        if (start % i) != 0:
            found = False
        i += 1
    start += 1

print("number {0:d}\n".format(start - 1))

print("time elapsed = {0:f} sec.\n".format((datetime.now() - t0).seconds))
Run Code Online (Sandbox Code Playgroud)

Bli*_*ixt 28

答案很简单,Python处理所有事物的对象,默认情况下它没有JIT.因此,通过修改堆栈上的几个字节并优化代码的热点部分(即迭代),而不是非常有效 - Python突出显示代表数字的丰富对象,而不是动态优化.

如果您在具有JIT的Python变体(例如,PyPy)中尝试过此操作,我保证您会看到巨大的差异.

一般提示是避免使用标准Python进行计算成本非常高的操作(特别是如果这是针对来自多个客户端的请求的后端).使用JIT的Java,C#,JavaScript等无比高效.

顺便说一句,如果你想以更Pythonic的方式编写你的例子,你可以这样做:

from datetime import datetime
start_time = datetime.now()

max_number = 20
x = max_number
while True:
    i = 2
    while i <= max_number:
        if x % i: break
        i += 1
    else:
        # x was not divisible by 2...20
        break
    x += 1

print('number:       %d' % x)
print('time elapsed: %d seconds' % (datetime.now() - start_time).seconds)
Run Code Online (Sandbox Code Playgroud)

以上为我执行了90秒.它更快的原因依赖于看似愚蠢的事情,例如x短时间start,我不经常分配变量,而且我依赖于Python自己的控制结构而不是变量检查来跳入/跳出循环.

  • 与此同时,我已经下载了"pypy"并尝试了原始代码; 执行时间降至10秒.然后尝试了你的代码(再次在pypy上)并将时间提高到5秒.所以,这与python本身有关. (4认同)
  • @ merkez3110:是的,正如我在回答中暗示的那样,PyPy有一个JIT,它基本上可以在更低的级别上优化代码.这消除了Python的许多问题,例如不是使用运算符方法对对象执行数学运算,而是直接对堆栈上的整数执行数学运算(如C#那样).说到堆栈,vanilla Python将变量存储在dict中,因此即使将`start`更改为`x`也可以提高vanilla Python的性能. (3认同)
  • 我发现,虽然some_expression:循环几乎总是可以通过使用some_iterable:中的item或range(...)中的for来加速。在这种情况下,`for i在range(2,max_number + 1):`中效果很好。较小的改进来自使用itertools.count(max_number):中的for x重写外部的while while True循环。 (2认同)

Mar*_*ark 6

如果您想要像 C 一样快但牺牲一点代码可读性,请尝试使用 python JIT 实现,例如 pypy 和 numba 或 cython。

例如在pypy

# PyPy

number 232792560

time elapsed = 4.000000 sec.
Run Code Online (Sandbox Code Playgroud)

例如在 cython

# Cython

number 232792560

time elapsed = 1.000000 sec.
Run Code Online (Sandbox Code Playgroud)

Cython 来源:

from datetime import datetime

cpdef void run():
    t0 = datetime.now()
    cdef int max_number = 20
    found = False
    cdef int start = max_number
    cdef int i
    while not found:
        found = True
        i = 2
        while ((i < max_number + 1) and found):
            if (start % i) != 0:
                found = False
            i += 1
        start += 1

    print("number {0:d}\n".format(start - 1))

    print("time elapsed = {0:f} sec.\n".format((datetime.now() - t0).seconds))
Run Code Online (Sandbox Code Playgroud)


Sol*_*tix 6

TL; DR:啰嗦的帖子就是我试图用C#来保护Python(我选择的语言).在这个例子中,C#表现得更好,但仍然需要更多行代码才能完成相同的工作量,但最终的性能优势是,如果编码正确,C#比Python中的类似方法快约5倍.最终结果是您应该使用适合您的语言.

当我运行C#示例时,我的机器上花了大约3秒钟完成,并给了我232,792,560的结果.它可以使用已知的事实进行优化,如果数字是20的倍数,您只能将数字从1到20整除,因此您不需要增加1,而是需要增加20.使代码在353毫秒内快速执行~10倍.

当我运行Python示例时,我放弃了等待,并尝试使用itertools编写我自己的版本,这没有更好的成功,并且只需要你的例子.然后我找到了一个可接受的itertools版本,如果我考虑到只有我的最大数字的倍数可以被从最小到最大的所有数字整除.因此,精炼的Python(3.6)代码在这里有一个装饰器计时功能,它打印执行所花费的秒数:

import time
from itertools import count, filterfalse


def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        print(time.time() - start)
        return res
    return wrapper


@timer
def test(stop):
    return next(filterfalse(lambda x: any(x%i for i in range(2, stop)), count(stop, stop)))


print("Test Function")
print(test(20))
# 11.526668787002563
# 232792560
Run Code Online (Sandbox Code Playgroud)

这也让我想起了我最近使用Python中最大公分母函数在CodeFights for Least Common Multiple上回答的一个问题.该代码如下:

import time
from fractions import gcd
from functools import reduce


def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        print(time.time() - start)
        return res
    return wrapper


@timer
def leastCommonDenominator(denominators):
    return reduce(lambda a, b: a * b // gcd(a, b), denominators)


print("LCM Function")
print(leastCommonDenominator(range(1, 21)))
# 0.001001596450805664
# 232792560
Run Code Online (Sandbox Code Playgroud)

与大多数编程任务一样,有时最简单的方法并不总是最快的.不幸的是,这次在Python中尝试时确实很突出.也就是说,Python中的优点在于获得高性能执行的简单性,其中需要10行C#,我能够(可能)返回一个单行lambda表达式中的正确答案,并且比我的300倍快. C#的简单优化.我不是C#的专家,但是在这里实现相同的方法是我使用的代码及其结果(比Python快约5倍):

using System;
using System.Diagnostics;

namespace ConsoleApp1
{
    class Program
    {
        public static void Main(string[] args)
        {
            Stopwatch t0 = new Stopwatch();
            int maxNumber = 20;

            long start;
            t0.Start();
            start = Orig(maxNumber);
            t0.Stop();

            Console.WriteLine("Original | {0:d}, {1:d}", maxNumber, start);
            // Original | 20, 232792560
            Console.WriteLine("Original | time elapsed = {0}.", t0.Elapsed);
            // Original | time elapsed = 00:00:02.0585575

            t0.Restart();
            start = Test(maxNumber);
            t0.Stop();

            Console.WriteLine("Test | {0:d}, {1:d}", maxNumber, start);
            // Test | 20, 232792560
            Console.WriteLine("Test | time elapsed = {0}.", t0.Elapsed);
            // Test | time elapsed = 00:00:00.0002763

            Console.ReadLine();
        }

        public static long Orig(int maxNumber)
        {
            bool found = false;
            long start = 0;
            while (!found)
            {
                start += maxNumber;
                found = true;
                for (int i=2; i < 21; i++)
                {
                    if (start % i != 0)
                        found = false;
                }
            }
            return start;
        }

        public static long Test(int maxNumber)
        {
            long result = 1;

            for (long i = 2; i <= maxNumber; i++)
            {
                result = (result * i) / GCD(result, i);
            }

            return result;
        }

        public static long GCD(long a, long b)
        {
            while (b != 0)
            {
                long c = b;
                b = a % b;
                a = c;
            }

            return a;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,对于大多数更高级别的任务,我通常认为Python与.NET实现相比表现得非常好,但我目前无法证实这些声明,除了说Python请求库给了我多达两倍的声明.与C#WebRequest相比,性能的三重回报以相同的方式编写.编写Selenium进程时也是如此,因为我可以在100毫秒或更短的时间内读取Python中的文本元素,但每个元素检索都需要C#> 1秒才能返回.也就是说,我实际上更喜欢C#实现,因为它采用面向对象的方法,Python的Selenium实现很有用,有时很难阅读.


小智 5

正如一些人所说,最好的方法是使用 JIT 实现。我知道这是一个老话题,但我很好奇实现之间的执行时间差异,所以我在 Jupiter Notebook 中用 Numba 和 Cython 做了一些测试,这是我的结果:

您在函数中的代码

%%time

def test():
    max_number = 20
    found = False
    start = max_number
    while not found:
        found = True
        i = 2
        while ((i < max_number + 1) and found):
            if (start % i) != 0:
                found = False
            i += 1
        start += 1
    return start-1
test()
Run Code Online (Sandbox Code Playgroud)

CPU 时间:用户 1 分 18 秒,系统:462 毫秒,总计:1 分 19 秒挂墙时间:1 分 21 秒

@Blixt 的写作方式

%%time

def test():
    max_number = 20
    x = max_number
    while True:
        i = 2
        while i <= max_number:
            if x % i: break
            i += 1
        else:
            # x was not divisible by 2...20
            break
        x += 1
    
    return x
    
test()
Run Code Online (Sandbox Code Playgroud)

CPU 时间:用户 40.1 秒,系统:305 毫秒,总计:40.4 秒挂墙时间:41.9 秒

努巴

%%time
from numba import jit

@jit(nopython=True)
def test():
    max_number = 20
    x = max_number
    while True:
        i = 2
        while i <= max_number:
            if x % i: break
            i += 1
        else:
            # x was not divisible by 2...20
            break
        x += 1
    
    return x
    
test()
Run Code Online (Sandbox Code Playgroud)

CPU 时间:用户 4.48 秒,系统:70.5 毫秒,总计:4.55 秒挂墙时间:5.01 秒

带有函数签名的 Numba

%%time
from numba import jit, int32

@jit(int32())
def test():
    max_number = 20
    x = max_number
    while True:
        i = 2
        while i <= max_number:
            if x % i: break
            i += 1
        else:
            # x was not divisible by 2...20
            break
        x += 1
    
    return x
    
test()
Run Code Online (Sandbox Code Playgroud)

CPU 时间:用户 3.56 秒,系统:43.1 毫秒,总计:3.61 秒挂墙时间:3.79 秒

赛通

%load_ext Cython
Run Code Online (Sandbox Code Playgroud)
%%time
%%cython

def test():
    cdef int max_number = 20
    cdef int x = max_number
    cdef int i = 2
    while True:
        i = 2
        while i <= max_number:
            if x % i: break
            i += 1
        else:
            # x was not divisible by 2...20
            break
        x += 1
    
    return x
    
test()
Run Code Online (Sandbox Code Playgroud)

CPU 时间:用户 617 毫秒,系统:20.7 毫秒,总计:637 毫秒挂墙时间:1.31 秒