为什么我在C语言中的计算速度比Python快得多

2024-05-20 23:38:03 发布

您现在位置:Python中文网/ 问答频道 /正文

下面是一个简单的过程,分别用C#Python编码(对于那些对这个过程感兴趣的人来说,这是Project Euler问题5的解决方案)。

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

到目前为止,我已经在C#Python中编写了类似的进程,并且执行时间的差异是可比较的。然而,这一次,经过的时间有一个极端的不同。

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

我做错什么了?

我的系统:Windows-7 64位

2012款速递(9秒)

Python3.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();
        }
    }
}

以及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))

Tags: 代码numberdatetime过程时间差异startnow
3条回答

如果您希望像C一样快,但是牺牲了一点代码可读性,那么可以尝试python JIT实现,比如pypypy和numba或cython。

例如在pypy中

# PyPy

number 232792560

time elapsed = 4.000000 sec.

例如在cython

# Cython

number 232792560

time elapsed = 1.000000 sec.

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

答案很简单,Python处理所有对象,默认情况下没有JIT。因此,不要通过修改堆栈上的几个字节和优化代码的热点部分(即迭代)来提高效率——Python会与表示数字的富对象一起运行,而不是动态优化。

如果您在一个具有JIT(例如PyPy)的Python变体中尝试了这个方法,我保证您会看到一个巨大的不同。

一般的技巧是避免使用标准的Python进行计算成本非常高的操作(特别是在后端为来自多个客户机的请求提供服务时)。使用JIT的Java、C#、JavaScript等效率更高。

顺便说一句,如果你想用一种更像Python的方式来写你的例子,你可以这样做:

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)

我在90秒内完成了上面的任务。它更快的原因在于看似愚蠢的事情,比如xstart短,我没有像以前那样频繁地分配变量,我依赖Python自己的控制结构,而不是变量检查来跳入/跳出循环。

TL;DR:冗长的文章,是我试图保护Python(我选择的语言)不受C#的攻击。在本例中,C#执行得更好,但仍然需要更多的代码行来完成相同的工作量,但最终的性能优势是,如果正确编码,C#比Python中类似的方法快大约5倍。最终的结果是你应该使用适合你的语言。

当我运行C#示例时,在我的机器上大约花了3秒钟完成,结果是232792560。如果一个数是20的倍数,那么它只能被1到20的数整除,因此不需要增加1,而需要增加20。这一次优化使代码在353毫秒内执行速度提高了约10倍。

当我运行Python示例时,我放弃了等待,尝试使用itertools编写自己的版本,这并没有获得更好的成功,而且花费的时间和您的示例差不多长。然后我找到了一个可以接受的itertools版本,如果我考虑到只有最大数的倍数才能被从最小到最大的所有数整除。因此,精制的Python(3.6)代码在这里提供了一个decorator计时函数,该函数打印执行所需的秒数:

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

这也让我想起了最近在Python中使用最大公分母函数为最小公倍数进行代码斗争时必须回答的一个问题。代码如下:

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

与大多数编程任务一样,有时最简单的方法并不总是最快的。不幸的是,这一次在Python中尝试时,它真的很突出。也就是说,Python的优点在于获得性能执行的简单性,它需要10行C,我能够(潜在地)在一行lambda表达式中返回正确的答案,比我在C上的简单优化快300倍。我不是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;
        }
    }
}

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

相关问题 更多 >