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

时间:2015-04-27 18:28:05

标签: c# python

以下是分别在C#Python中编码的简单流程(对于那些对此过程感到好奇的人,它是{{3}的问题5的解决方案}})。

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

到目前为止,我已经在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();
        }
    }
}

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

6 个答案:

答案 0 :(得分:24)

答案很简单,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)

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

答案 1 :(得分: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

这也让我想起了我最近在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

与大多数编程任务一样,有时最简单的方法并不总是最快的。不幸的是,这次在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;
        }
    }
}

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

答案 2 :(得分:3)

尝试python JIT实现,如pypy和numba或cython,如果你想快速为C但牺牲了一些代码可读性。

例如在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))

答案 3 :(得分:2)

正如某些人所说,最好的方法是使用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()

CPU时间:用户1min 18s,sys:462 ms,总计:1min 19s 挂墙时间: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()

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

Numba

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

CPU时间:用户4.48 s,sys:70.5 ms,总计:4.55 s 挂墙时间:5.01 s

具有功能签名的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()

CPU时间:用户3.56 s,sys:43.1 ms,总计:3.61 s 挂墙时间:3.79 s

Cython

%load_ext Cython
%%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()

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

答案 4 :(得分:0)

Python(以及包括matlab在内的所有脚本语言)并非旨在用于大规模数值计算。要获得兼容的结果作为编译程序,不惜一切代价避免循环并将公式转换为矩阵格式(需要一点数学理解和技巧),以便我们可以尽可能多地推送到后台C库由numpy,scipy等提供

同样,只要矩阵等效,就不要为python 中的数值计算编写循环!

答案 5 :(得分:0)

首先,您需要更改算法以解决此问题:

#!/usr/bin/env python

import sys
from timeit import default_timer as timer

pyver = sys.version_info;

print(">")
print(">  Smallest multiple of 2 ... K");
print(">")
print(">  Python version, interpreter version: {0}.{1}.{2}-{3}-{4}".format(
    pyver.major, pyver.minor, pyver.micro, pyver.releaselevel, pyver.serial))
print(">")

K = 20;

print("  K = {0:d}".format(K))
print("")

t0 = timer()

N = K
NP1 = N + 1
N2 = (N >> 1) + 1
vec = range(0, NP1)
smalestMultiple = 1

for i in range(2, N2):
    divider = vec[i]
    if divider == 1:
        continue
    for j in range(i << 1, NP1, i):
        if (vec[j] % divider) == 0:
            vec[j] /= divider

for i in range(2, NP1):
    if vec[i] != 1:
        smalestMultiple = smalestMultiple * vec[i]

t1 = timer()

print("  smalest multiple = {0:d}".format(smalestMultiple))
print("  time elapsed = {0:f} sec.".format(t1 - t0))

Linux / Fedora 28 /Intel®Core™i7-2760QM CPU @ 2.40GHz上的输出:

>  Smallest multiple of 2 ... K
>
>  Python version, interpreter version: 2.7.15-final-0
>
>  K = 20
>
>  smalest multiple = 232792560
>  time elapsed = 0.000032 sec.