Project Euler: C vs Python vs Erlang vs Haskell ile hız karşılaştırması


672

Ben almış bir problemi # 12 dan Projesi Euler bir programlama egzersiz ve C, Python, Erlang ve Haskell uygulamaları benim (mutlaka değil optimal olan) karşılaştırmak. Daha yüksek yürütme süreleri elde etmek için, orijinal problemde belirtildiği gibi 500 yerine 1000'den fazla bölücü içeren ilk üçgen numarasını ararım.

Sonuç şudur:

C:

lorenzo@enzo:~/erlang$ gcc -lm -o euler12.bin euler12.c
lorenzo@enzo:~/erlang$ time ./euler12.bin
842161320

real    0m11.074s
user    0m11.070s
sys 0m0.000s

Python:

lorenzo@enzo:~/erlang$ time ./euler12.py 
842161320

real    1m16.632s
user    1m16.370s
sys 0m0.250s

PyPy ile Python:

lorenzo@enzo:~/Downloads/pypy-c-jit-43780-b590cf6de419-linux64/bin$ time ./pypy /home/lorenzo/erlang/euler12.py 
842161320

real    0m13.082s
user    0m13.050s
sys 0m0.020s

Erlang:

lorenzo@enzo:~/erlang$ erlc euler12.erl 
lorenzo@enzo:~/erlang$ time erl -s euler12 solve
Erlang R13B03 (erts-5.7.4) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.4  (abort with ^G)
1> 842161320

real    0m48.259s
user    0m48.070s
sys 0m0.020s

Haskell:

lorenzo@enzo:~/erlang$ ghc euler12.hs -o euler12.hsx
[1 of 1] Compiling Main             ( euler12.hs, euler12.o )
Linking euler12.hsx ...
lorenzo@enzo:~/erlang$ time ./euler12.hsx 
842161320

real    2m37.326s
user    2m37.240s
sys 0m0.080s

Özet:

  • C:% 100
  • Python:% 692 (PyPy ile% 118)
  • Erlang:% 436 (RichardC sayesinde% 135)
  • Haskell:% 1421

C'nin hesaplamalar için uzun kullandığı ve diğer üçü gibi keyfi uzunluk tamsayıları olmadığı için büyük bir avantajı olduğunu düşünüyorum. Ayrıca önce bir çalışma zamanı yüklemesine gerek yoktur (Diğerleri mi?).

Soru 1: Erlang, Python ve Haskell, keyfi uzunluk tamsayıları nedeniyle hız kaybediyor MAXINTmu yoksa değerler daha az olduğu sürece hızını kaybetmiyor mu?

Soru 2: Haskell neden bu kadar yavaş? Frenleri kapatan bir derleyici bayrağı var mı yoksa benim uygulamam mı? (Haskell bana yedi mührü olan bir kitap olduğu için ikincisi oldukça muhtemel.

Soru 3: Faktörleri belirleme şeklimizi değiştirmeden bu uygulamaları nasıl optimize edeceğime dair bazı ipuçları verebilir misiniz? Herhangi bir şekilde optimizasyon: daha güzel, daha hızlı, dile daha "yerli".

DÜZENLE:

Soru 4: İşlevsel uygulamalarım LCO'ya (son çağrı optimizasyonu, kuyruk özyineleme ortadan kaldırılması) izin veriyor ve bu nedenle çağrı yığınına gereksiz kareler eklemekten kaçınıyor mu?

Haskell ve Erlang bilgimin çok sınırlı olduğunu itiraf etmeliyim de, dört dilde mümkün olduğunca aynı algoritmayı uygulamaya çalıştım.


Kullanılan kaynak kodları:

#include <stdio.h>
#include <math.h>

int factorCount (long n)
{
    double square = sqrt (n);
    int isquare = (int) square;
    int count = isquare == square ? -1 : 0;
    long candidate;
    for (candidate = 1; candidate <= isquare; candidate ++)
        if (0 == n % candidate) count += 2;
    return count;
}

int main ()
{
    long triangle = 1;
    int index = 1;
    while (factorCount (triangle) < 1001)
    {
        index ++;
        triangle += index;
    }
    printf ("%ld\n", triangle);
}

#! /usr/bin/env python3.2

import math

def factorCount (n):
    square = math.sqrt (n)
    isquare = int (square)
    count = -1 if isquare == square else 0
    for candidate in range (1, isquare + 1):
        if not n % candidate: count += 2
    return count

triangle = 1
index = 1
while factorCount (triangle) < 1001:
    index += 1
    triangle += index

print (triangle)

-module (euler12).
-compile (export_all).

factorCount (Number) -> factorCount (Number, math:sqrt (Number), 1, 0).

factorCount (_, Sqrt, Candidate, Count) when Candidate > Sqrt -> Count;

factorCount (_, Sqrt, Candidate, Count) when Candidate == Sqrt -> Count + 1;

factorCount (Number, Sqrt, Candidate, Count) ->
    case Number rem Candidate of
        0 -> factorCount (Number, Sqrt, Candidate + 1, Count + 2);
        _ -> factorCount (Number, Sqrt, Candidate + 1, Count)
    end.

nextTriangle (Index, Triangle) ->
    Count = factorCount (Triangle),
    if
        Count > 1000 -> Triangle;
        true -> nextTriangle (Index + 1, Triangle + Index + 1)  
    end.

solve () ->
    io:format ("~p~n", [nextTriangle (1, 1) ] ),
    halt (0).

factorCount number = factorCount' number isquare 1 0 - (fromEnum $ square == fromIntegral isquare)
    where square = sqrt $ fromIntegral number
          isquare = floor square

factorCount' number sqrt candidate count
    | fromIntegral candidate > sqrt = count
    | number `mod` candidate == 0 = factorCount' number sqrt (candidate + 1) (count + 2)
    | otherwise = factorCount' number sqrt (candidate + 1) count

nextTriangle index triangle
    | factorCount triangle > 1000 = triangle
    | otherwise = nextTriangle (index + 1) (triangle + index + 1)

main = print $ nextTriangle 1 1

55
@Jochen (ve Seth) Gerçekten C'nin hızlı veya harika olduğu değil, ancak performans kodu yazmak kolay olarak algılanıyor (bu doğru olmayabilir, ancak çoğu program mümkün, yeterince doğru görünüyor). Cevabımda araştırdığımda ve zaman içinde doğru olarak bulduğumda, programcı beceri ve seçilen dil için ortak optimizasyon bilgisi çok önemlidir (özellikle Haskell için).
Thomas M. DuBuisson

52
Sadece ile kontrol Mathematica - o 0.25sec alır (C burada 6sec sürer) ve kod sadece geçerli: Euler12[x_Integer] := Module[{s = 1}, For[i = 2, DivisorSigma[0, s] < x, i++, s += i]; s]. yaşasın!
tsvikas

35
Dışarıda C ve meclis arasındaki bu savaşları hatırlayan başka biri var mı? "Elbette! Kodunuzu 10 kat daha hızlı yazabilirsiniz, ancak C kodunuz bu kadar hızlı çalışabilir mi? ..." Eminim aynı kodlar makine kodu ve montaj arasında da yapılmıştır.
JS.

39
@JS: Muhtemelen hayır, montaj basitçe ham ikili makine kodu yerine yazdığınız bir dizi anımsatıcı olduğundan - normalde aralarında 1-1 bir yazışma vardır.
Callum Rogers

9
Sonuç, Haskell için: -O2 ona yaklaşık 3 kat bir hız kazandırır ve 12x-14x ve daha fazla toplam hız için Tamsayı yerine 4x-6x yerine Int kullanır.
Ness

Yanıtlar:


794

Kullanarak GHC 7.0.3, gcc 4.4.6, Linux 2.6.29, bir x86_64 Core 2 Duo (2,5 GHz) makinede kullanarak derleme ghc -O2 -fllvm -fforce-recompHaskell'e ve gcc -O3 -lmC için

  • C rutininiz 8.4 saniye içinde çalışır (çünkü muhtemelen çalışmanızdan daha hızlıdır -O3)
  • Haskell çözümü 36 saniye içinde çalışıyor ( -O2bayrağa bağlı olarak )
  • Kişisel factorCount'kod açıkça yazmış ve varsaymak değil Integer(benim Yanlış teşhisten düzeltmek için Daniel sayesinde!). Kullanarak açık bir tip imzası (yine de standart uygulamadır) Intve zaman 11,1 saniyeye değişir
  • içinde factorCount'gereksiz yere aradın fromIntegral. Bir düzeltme hiçbir değişikliğe neden olmaz (derleyici akıllı, sizin için şanslı).
  • Daha hızlı ve yeterli olan modyerleri kullandınız rem. Bu, süreyi 8,5 saniyeye değiştirir .
  • factorCount'sürekli değişmeyen iki ekstra argüman uyguluyor ( number, sqrt). Bir işçi / sarıcı dönüşümü bize şunları sağlar:
 $ time ./so
 842161320  

 real    0m7.954s  
 user    0m7.944s  
 sys     0m0.004s  

Doğru, 7.95 saniye . C çözeltisinden sürekli olarak yarım saniye daha hızlı . -fllvmBayrak olmadan hala alıyorum 8.182 seconds, bu nedenle NCG arka ucu da bu durumda iyi gidiyor .

Sonuç: Haskell harika.

Sonuç Kodu

factorCount number = factorCount' number isquare 1 0 - (fromEnum $ square == fromIntegral isquare)
    where square = sqrt $ fromIntegral number
          isquare = floor square

factorCount' :: Int -> Int -> Int -> Int -> Int
factorCount' number sqrt candidate0 count0 = go candidate0 count0
  where
  go candidate count
    | candidate > sqrt = count
    | number `rem` candidate == 0 = go (candidate + 1) (count + 2)
    | otherwise = go (candidate + 1) count

nextTriangle index triangle
    | factorCount triangle > 1000 = triangle
    | otherwise = nextTriangle (index + 1) (triangle + index + 1)

main = print $ nextTriangle 1 1

DÜZENLEME: Şimdi araştırdık, soruları ele alalım

Soru 1: Erlang, python ve haskell, keyfi uzunluk tamsayıları nedeniyle hız kaybediyor mu veya değerler MAXINT'den az olduğu sürece mi?

Haskell'de, kullanmak Integerdaha yavaş Intama ne kadar yavaş yapılırsa, yapılan hesaplamalara bağlıdır. Neyse ki (64 bit makineler için) Intyeterlidir. Taşınabilirlik uğruna, kodumu kullanmak için muhtemelen yeniden yazmalısınız Int64veya Word64(C, a olan tek dil değildir long).

Soru 2: Haskell neden bu kadar yavaş? Frenleri kapatan bir derleyici bayrağı var mı yoksa benim uygulamam mı? (Haskell bana yedi mühürlü bir kitap olduğundan, ikincisi oldukça muhtemeldir.)

Soru 3: Faktörleri belirleme şeklimizi değiştirmeden bu uygulamaları nasıl optimize edeceğime dair bazı ipuçları verebilir misiniz? Herhangi bir şekilde optimizasyon: daha güzel, daha hızlı, dile daha "yerli".

Yukarıda cevapladığım buydu. Cevap şuydu:

  • 0) üzerinden optimizasyonu kullanın -O2
  • 1) Mümkünse hızlı (özellikle kutudan çıkabilen) türleri kullanın
  • 2) remdeğil mod(sıkça unutulan bir optimizasyon) ve
  • 3) işçi / sarıcı dönüşümü (belki de en yaygın optimizasyon).

Soru 4: İşlevsel uygulamalarım LCO'ya izin veriyor ve bu nedenle çağrı yığınına gereksiz kareler eklemekten kaçınıyor mu?

Evet, sorun bu değildi. İyi iş çıkardınız ve bunu düşündüğünüze sevindim.


25
@Karl Çünkü remaslında işlemin bir alt bileşenidir mod(bunlar aynı değildir). GHC Base kütüphanesine bakarsanız, modbirkaç koşul için testler görür ve işareti buna göre ayarlar. (bkz modInt#içinde Base.lhs)
Thomas M. DUBUISSON

20
Başka bir veri noktası: @ Hyperboreus'un Haskell'ine bakmadan C programının hızlı bir Haskell çevirisi yazdım . Bu yüzden biraz daha yakın standart deyimsel Haskell için, ve ben kasten eklenen tek optimizasyon değiştiriliyor modile rembu cevap (heh, hop) okuduktan sonra. Zamanlamalarım için bağlantıya bakın, ancak kısa sürüm "C ile neredeyse aynı".
CA McCann

106
C versiyonunun makinemde daha hızlı çalıştığını düşünsem bile, şimdi Haskell'e yeni bir saygı duyuyorum. +1
Seth Carnegie

11
Henüz denememiş olmama rağmen bu benim için oldukça şaşırtıcı. Orijinal factorCount'kuyruk özyinelemeli olduğundan, derleyici ekstra parametrelerin değiştirilmemesini fark edebilir ve kuyruk özyinelemesini sadece değişen parametreler için optimize edebilirdi (Haskell sonuçta saf bir dil, bu kolay olmalı). Herkes derleyicinin bunu yapabileceğini düşünüyor ya da daha fazla teorik makale okumak için geri dönmeli miyim?
kizzx2

22
@ kizzx2: Eklenmesi için bir GHC bileti var. Anladığım kadarıyla, bu dönüşüm ek kapatma nesneleri tahsisi ile sonuçlanabilir. Bu, bazı durumlarda daha kötü performans anlamına gelir, ancak Johan Tibell'in blog gönderisinde önerdiği gibi , sonuçta ortaya çıkan sargı çizgisi çizilebiliyorsa bu önlenebilir.
hammar

224

Erlang uygulamasında bazı sorunlar var. Aşağıdakiler için temel olarak, değiştirilmemiş Erlang programınız için ölçülen yürütme sürem C kodu için 12.7 saniyeye kıyasla 47.6 saniyeydi.

Hesaplamalı olarak yoğun Erlang kodunu çalıştırmak istiyorsanız yapmanız gereken ilk şey yerel kodu kullanmaktır. İle derleme erlc +native euler12süresi 41.3 saniyeye düştü. Ancak bu, bu tür kodlarda yerel derlemeden beklenenden çok daha düşük bir hızdır (sadece% 15) ve sorun kullanımınızdır -compile(export_all). Bu, deneyler için yararlıdır, ancak tüm işlevlerin dışarıdan potansiyel olarak erişilebilir olması, yerel derleyicinin çok muhafazakar olmasına neden olur. (Normal BEAM öykünücüsü o kadar etkilenmez.) Bu bildirimi değiştirmek -export([solve/0]).daha iyi bir hızlanma sağlar: 31.5 saniye (taban çizgisinden neredeyse% 35).

Ancak kodun kendisinde bir sorun vardır: factorCount döngüsünde her yineleme için bu testi gerçekleştirirsiniz:

factorCount (_, Sqrt, Candidate, Count) when Candidate == Sqrt -> Count + 1;

C kodu bunu yapmaz. Genel olarak, aynı kodun farklı uygulamaları arasında ve özellikle algoritmanın sayısal olması durumunda adil bir karşılaştırma yapmak zor olabilir, çünkü aslında aynı şeyi yaptığından emin olmanız gerekir. Bir uygulamada bir yerlerde bazı yazım hataları nedeniyle hafif bir yuvarlama hatası, her ikisi de sonunda aynı sonuca ulaşsa bile, diğerinden daha fazla yineleme yapmasına neden olabilir.

Bu olası hata kaynağını ortadan kaldırmak (ve her bir yinelemedeki ekstra testten kurtulmak için), C kodu üzerinde yakından modellenen factorCount işlevini aşağıdaki gibi yeniden yazdım:

factorCount (N) ->
    Sqrt = math:sqrt (N),
    ISqrt = trunc(Sqrt),
    if ISqrt == Sqrt -> factorCount (N, ISqrt, 1, -1);
       true          -> factorCount (N, ISqrt, 1, 0)
    end.

factorCount (_N, ISqrt, Candidate, Count) when Candidate > ISqrt -> Count;
factorCount ( N, ISqrt, Candidate, Count) ->
    case N rem Candidate of
        0 -> factorCount (N, ISqrt, Candidate + 1, Count + 2);
        _ -> factorCount (N, ISqrt, Candidate + 1, Count)
    end.

Bu yeniden yazma, hayır export_allve yerel derleme bana aşağıdaki çalışma süresini verdi:

$ erlc +native euler12.erl
$ time erl -noshell -s euler12 solve
842161320

real    0m19.468s
user    0m19.450s
sys 0m0.010s

C koduna göre çok kötü değil:

$ time ./a.out 
842161320

real    0m12.755s
user    0m12.730s
sys 0m0.020s

Erlang'ın sayısal kod yazmaya yönelik olmadığı düşünülürse, böyle bir programda C'den sadece% 50 daha yavaş olması oldukça iyidir.

Son olarak, sorularınızla ilgili olarak:

Soru 1: Erlang, python ve haskell rasgele uzunluk tamsayıları kullanması nedeniyle hız kaybediyor mu yoksa değerler MAXINT'den düşük mü?

Evet, biraz. Erlang'da, "etrafı saran 32/64-bit aritmetik kullanın" demenin bir yolu yoktur, bu nedenle derleyici tamsayılarınız üzerinde bazı sınırlar kanıtlayamadıkça (ve genellikle yapamaz), görmek için tüm hesaplamaları kontrol etmelidir tek bir etiketli kelimeye sığabiliyorsa veya bunları öbek tahsis edilmiş bignumlara dönüştürmesi gerekiyorsa. Çalışma zamanında pratikte hiç bignum kullanılmasa bile, bu kontrollerin yapılması gerekecektir. Öte yandan, aniden eskisinden daha büyük girişler verirseniz, beklenmedik bir tamsayı sarmalaması nedeniyle algoritmanın asla başarısız olmayacağını bilirsiniz .

Soru 4: İşlevsel uygulamalarım LCO'ya izin veriyor ve bu nedenle çağrı yığınına gereksiz kareler eklemekten kaçınıyor mu?

Evet, Erlang kodunuz son arama optimizasyonuna göre doğrudur.


2
Size katılıyorum. Bu kıyaslama özellikle Erlang için bir dizi nedenden dolayı kesin değildi
Muzaaya Joshua

156

Python optimizasyonu ile ilgili olarak, PyPy (kodunuzda sıfır değişiklik yapan oldukça etkileyici hızlandırmalar için) kullanmaya ek olarak, bir RPython uyumlu sürüm derlemek için PyPy'nin çeviri araç zincirini veya her ikisinin de bir uzantı modülü oluşturmak için Cython'u kullanabilirsiniz. Testlerimdeki C versiyonundan daha hızlı olan Cython modülü neredeyse iki kat daha hızlı . Referans için C ve PyPy karşılaştırma sonuçlarını da dahil ediyorum:

C (ile derlendi gcc -O3 -lm)

% time ./euler12-c 
842161320

./euler12-c  11.95s 
 user 0.00s 
 system 99% 
 cpu 11.959 total

PyPy 1.5

% time pypy euler12.py
842161320
pypy euler12.py  
16.44s user 
0.01s system 
99% cpu 16.449 total

RPython (en son PyPy revizyonunu kullanarak c2f583445aee)

% time ./euler12-rpython-c
842161320
./euler12-rpy-c  
10.54s user 0.00s 
system 99% 
cpu 10.540 total

Cython 0.15

% time python euler12-cython.py
842161320
python euler12-cython.py  
6.27s user 0.00s 
system 99% 
cpu 6.274 total

RPython sürümünde birkaç önemli değişiklik var. Bağımsız bir programa çevirmek için target, bu durumda mainişlev olan bilgisayarınızı tanımlamanız gerekir . sys.argvTek argüman olduğu için kabul etmesi bekleniyor ve bir int döndürmek gerekiyor. % translate.py euler12-rpython.pyC'ye çeviren ve sizin için derleyen translate.py dosyasını kullanarak çevirebilirsiniz .

# euler12-rpython.py

import math, sys

def factorCount(n):
    square = math.sqrt(n)
    isquare = int(square)
    count = -1 if isquare == square else 0
    for candidate in xrange(1, isquare + 1):
        if not n % candidate: count += 2
    return count

def main(argv):
    triangle = 1
    index = 1
    while factorCount(triangle) < 1001:
        index += 1
        triangle += index
    print triangle
    return 0

if __name__ == '__main__':
    main(sys.argv)

def target(*args):
    return main, None

Cython sürümü, _euler12.pyxnormal bir python dosyasından alıp çağırdığım bir genişletme modülü olarak yeniden yazıldı . _euler12.pyxEsasen bazı ek statik türünde açıklamalarla Sürümünüze aynıdır. Setup.py, uzantıyı kullanarak normal bir kazan plakasına sahiptir python setup.py build_ext --inplace.

# _euler12.pyx
from libc.math cimport sqrt

cdef int factorCount(int n):
    cdef int candidate, isquare, count
    cdef double square
    square = sqrt(n)
    isquare = int(square)
    count = -1 if isquare == square else 0
    for candidate in range(1, isquare + 1):
        if not n % candidate: count += 2
    return count

cpdef main():
    cdef int triangle = 1, index = 1
    while factorCount(triangle) < 1001:
        index += 1
        triangle += index
    print triangle

# euler12-cython.py
import _euler12
_euler12.main()

# setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension("_euler12", ["_euler12.pyx"])]

setup(
  name = 'Euler12-Cython',
  cmdclass = {'build_ext': build_ext},
  ext_modules = ext_modules
)

Dürüst olmak gerekirse RPython veya Cython ile çok az deneyimim var ve sonuçlara hoş bir sürpriz oldu. CPython kullanıyorsanız, CPU yoğun kod parçalarınızı bir Cython genişletme modülüne yazmak, programınızı optimize etmenin gerçekten kolay bir yolu gibi görünüyor.


6
Merak ediyorum, C sürümü en azından CPython kadar hızlı olacak şekilde optimize edilebilir mi?
Görünen Ad

4
@SargeBorsch, Cython sürümünün çok hızlı olması nedeniyle son derece optimize edilmiş bir C kaynağına derlendiğinden, bu performansı C'den çıkarabileceğinizden emin olabilirsiniz
Eli Korvigo

72

Soru 3: Faktörleri belirleme şeklimizi değiştirmeden bu uygulamaları nasıl optimize edeceğime dair bazı ipuçları verebilir misiniz? Herhangi bir şekilde optimizasyon: daha güzel, daha hızlı, dile daha "yerli".

C uygulaması yetersizdir (Thomas M. DuBuisson tarafından ima edildiği gibi), sürüm 64 bit tamsayılar kullanır (yani uzun veri türü). Derleme listesini daha sonra araştıracağım, ancak eğitimli bir tahminle, derlenmiş kodda devam eden bazı bellek erişimleri var, bu da 64 bit tam sayıları kullanmayı önemli ölçüde yavaşlatıyor. Bu ya da oluşturulan kod (bir SSE kaydına daha az 64-bit int sığabilmeniz ya da bir çiftin 64-bit tam sayıya yuvarlanabilmesi gerçeği daha yavaştır).

İşte değiştirilmiş kodu (sadece int ile uzun değiştirin ve bu gcc -O3 ile gerekli olduğunu düşünmüyorum, ancak açıkça inlined factorCount):

#include <stdio.h>
#include <math.h>

static inline int factorCount(int n)
{
    double square = sqrt (n);
    int isquare = (int)square;
    int count = isquare == square ? -1 : 0;
    int candidate;
    for (candidate = 1; candidate <= isquare; candidate ++)
        if (0 == n % candidate) count += 2;
    return count;
}

int main ()
{
    int triangle = 1;
    int index = 1;
    while (factorCount (triangle) < 1001)
    {
        index++;
        triangle += index;
    }
    printf ("%d\n", triangle);
}

Koşu + zamanlama verir:

$ gcc -O3 -lm -o euler12 euler12.c; time ./euler12
842161320
./euler12  2.95s user 0.00s system 99% cpu 2.956 total

Referans olarak, daha önceki cevapta Thomas'ın haskell uygulaması şunları verir:

$ ghc -O2 -fllvm -fforce-recomp euler12.hs; time ./euler12                                                                                      [9:40]
[1 of 1] Compiling Main             ( euler12.hs, euler12.o )
Linking euler12 ...
842161320
./euler12  9.43s user 0.13s system 99% cpu 9.602 total

Sonuç: ghc'den hiçbir şey almamak harika bir derleyicidir ancak gcc normalde daha hızlı kod üretir.


22
Çok hoş! Karşılaştırma için, makinemde C çözümünüz 2.5 secondsHaskell kodunda benzer bir değişiklik yaparken (Word32'ye geçerek, INLINE pragma ekleyerek) bir çalışma zamanı ile sonuçlanır 4.8 seconds. Belki bir şey yapılabilir (üçüncüsü değil, öyle görünüyor) - gcc sonucu kesinlikle etkileyici.
Thomas M. DuBuisson

1
Teşekkürler! Belki de soru, gerçek dilin kendisi yerine çeşitli derleyiciler tarafından derlenen çıktının hızı olmalıdır. Daha sonra, Intel kılavuzlarını çıkarmak ve elle optimize etmek yine de kazanacaktır (bilgi ve zamana sahip olmanız şartıyla (çok fazla)).
Raedwulf

56

Bu bloga bir göz atın . Geçen yıl ya da öyle olduğunu Haskell ve Python Proje Euler sorunlar birkaç yapıldığını ve o genellikle bulduğunu Haskell çok daha hızlı olması. Bu diller arasında akıcılık ve kodlama tarzınızla daha fazla ilgisi olduğunu düşünüyorum.

Python hızı söz konusu olduğunda, yanlış uygulamayı kullanıyorsunuz! PyPy'yi deneyin ve bunun gibi şeyler için çok daha hızlı olduğunu göreceksiniz.


32

Haskell uygulamanız, Haskell paketlerinden bazı işlevler kullanılarak büyük ölçüde hızlandırılabilir. Bu durumda, sadece 'cabal kurulum astarları' ile kurulan astarları kullandım;)

import Data.Numbers.Primes
import Data.List

triangleNumbers = scanl1 (+) [1..]
nDivisors n = product $ map ((+1) . length) (group (primeFactors n))
answer = head $ filter ((> 500) . nDivisors) triangleNumbers

main :: IO ()
main = putStrLn $ "First triangle number to have over 500 divisors: " ++ (show answer)

Zamanlamaları:

Orijinal programınız:

PS> measure-command { bin\012_slow.exe }

TotalSeconds      : 16.3807409
TotalMilliseconds : 16380.7409

Geliştirilmiş uygulama

PS> measure-command { bin\012.exe }

TotalSeconds      : 0.0383436
TotalMilliseconds : 38.3436

Gördüğünüz gibi, bu makine aynı makinede 38 milisaniyede çalışıyor, burada 16 saniye içinde koştu :)

Derleme komutları:

ghc -O2 012.hs -o bin\012.exe
ghc -O2 012_slow.hs -o bin\012_slow.exe

5
Son kontrol Haskell "primes" sadece önceden hesaplanmış primler büyük bir liste oldu - hesaplama, sadece arama. Yani evet, elbette bu daha hızlı olacak, ancak Haskell'de primer türetmenin hesaplama hızı hakkında hiçbir şey söylemiyor .
zxq9

21
@ zxq9, asal paket kaynağında ( hackage.haskell.org/package/primes-0.2.1.0/docs/src/… ) asal sayılar listesinin nerede olduğunu söyleyebilir misiniz ?
Fraser

4
Kaynak, primerlerin önceden hesaplanmadığını gösterirken, bu hızlanma tamamen delidir, C versiyonundan mil daha hızlıdır, bu yüzden ne halt oluyor?
noktalı virgül

1
@semicolon ezberleme. Bu durumda Haskell'in çalışma zamanında tüm primerleri ezberlediğini düşünüyorum, böylece her yinelemeyi yeniden hesaplamak zorunda kalmıyor.
Mart'ta Hauleth

5
500 değil 1000 bölen.
Casper Færgemand

29

Sadece eğlence için. Aşağıda daha 'yerel' bir Haskell uygulaması yer almaktadır:

import Control.Applicative
import Control.Monad
import Data.Either
import Math.NumberTheory.Powers.Squares

isInt :: RealFrac c => c -> Bool
isInt = (==) <$> id <*> fromInteger . round

intSqrt :: (Integral a) => a -> Int
--intSqrt = fromIntegral . floor . sqrt . fromIntegral
intSqrt = fromIntegral . integerSquareRoot'

factorize :: Int -> [Int]
factorize 1 = []
factorize n = first : factorize (quot n first)
  where first = (!! 0) $ [a | a <- [2..intSqrt n], rem n a == 0] ++ [n]

factorize2 :: Int -> [(Int,Int)]
factorize2 = foldl (\ls@((val,freq):xs) y -> if val == y then (val,freq+1):xs else (y,1):ls) [(0,0)] . factorize

numDivisors :: Int -> Int
numDivisors = foldl (\acc (_,y) -> acc * (y+1)) 1 <$> factorize2

nextTriangleNumber :: (Int,Int) -> (Int,Int)
nextTriangleNumber (n,acc) = (n+1,acc+n+1)

forward :: Int -> (Int, Int) -> Either (Int, Int) (Int, Int)
forward k val@(n,acc) = if numDivisors acc > k then Left val else Right (nextTriangleNumber val)

problem12 :: Int -> (Int, Int)
problem12 n = (!!0) . lefts . scanl (>>=) (forward n (1,1)) . repeat . forward $ n

main = do
  let (n,val) = problem12 1000
  print val

ghc -O3Bunu kullanarak , makinemde sürekli olarak 0,55-0,58 saniye içinde çalışır (1,73GHz Core i7).

C sürümü için daha verimli factorCount işlevi:

int factorCount (int n)
{
  int count = 1;
  int candidate,tmpCount;
  while (n % 2 == 0) {
    count++;
    n /= 2;
  }
    for (candidate = 3; candidate < n && candidate * candidate < n; candidate += 2)
    if (n % candidate == 0) {
      tmpCount = 1;
      do {
        tmpCount++;
        n /= candidate;
      } while (n % candidate == 0);
       count*=tmpCount;
      }
  if (n > 1)
    count *= 2;
  return count;
}

gcc -O3 -lmUzunları ana olarak ints olarak değiştirmek, kullanarak , sürekli olarak 0.31-0.35 saniye içinde çalışır.

Eğer nth üçgen sayısı = n * (n + 1) / 2 ve n ve (n + 1) tamamen birbirinden farklı asal çarpanlara sahip olmalarından yararlanırsanız, her ikisi de daha hızlı çalışabilir, bu nedenle faktör sayısı bütün yarının faktörlerinin sayısını bulmak için her bir yarının çarpımı elde edilebilir. Aşağıdaki:

int main ()
{
  int triangle = 0,count1,count2 = 1;
  do {
    count1 = count2;
    count2 = ++triangle % 2 == 0 ? factorCount(triangle+1) : factorCount((triangle+1)/2);
  } while (count1*count2 < 1001);
  printf ("%lld\n", ((long long)triangle)*(triangle+1)/2);
}

c kodu çalışma süresini 0.17-0.19 saniyeye düşürür ve çok daha büyük aramaları işleyebilir - makinemde 10000 faktörden büyük yaklaşık 43 saniye sürer. İlgili okuyucuya benzer bir haskell hızlandırması bırakıyorum.


3
Sadece karşılaştırma için: orijinal c versiyonu: 9.1690, thaumkid'in versiyonu: 0.1060 86x iyileştirme.
thanos


Aslında bunu yapan çıkarım değildir. Bu sadece size yardımcı olur A) hata ayıklama veya yazım sorunları ve yazım sınıfı örnek seçme sorunları B) hata ayıklama ve birkaç modern dil uzantıları ile bazı kararsız tür sorunları önlemek. Ayrıca, geliştirme çabalarınızı asla ölçeklendirmemeniz için programlarınızı karmaşık hale getirmenize yardımcı olur.
codeshot

Intel kafatası kanyon c sürümü 0.11 s
codeshot

13
Soru 1: Erlang, python ve haskell rasgele uzunluk tamsayıları kullanması nedeniyle hız kaybediyor mu yoksa değerler MAXINT'den düşük mü?

Bu pek olası değil. Erlang ve Haskell hakkında çok şey söyleyemem (belki de aşağıda Haskell hakkında biraz bilgi verebilirim) ama Python'daki diğer darboğazlara işaret edebilirim. Program Python'da bazı değerlerle bir işlem gerçekleştirmeye çalıştığında, değerlerin uygun türden olup olmadığını doğrulamalı ve biraz zamana mal olmalıdır. Sizin factorCountfonksiyonu sadece bir liste ayırır range (1, isquare + 1)çeşitli zamanlarda ve çalışma zamanı, mallocsen Özellikle C olduğu gibi -styled bellek ayırma yolu, bir sayaç ile yavaş iterating daha bir dizi olduğunu factorCount()defalarca aradım ve böylece listelerin bir sürü ayırır edilir. Ayrıca, Python'un yorumlandığını ve CPython yorumlayıcısının optimize edilmeye odaklanmadığını da unutmayalım.

EDIT : oh, iyi, Python 3 kullandığınızı unutmayın, bu yüzden range()bir liste değil, bir jeneratör döndürür. Bu durumda, listeleri ayırma konusundaki benim düşüncem yarı yanlış: işlev sadece rangeyine de verimsiz olan, ancak çok sayıda öğe içeren bir liste ayırmak kadar verimsiz olmayan nesneleri ayırır.

Soru 2: Haskell neden bu kadar yavaş? Frenleri kapatan bir derleyici bayrağı var mı yoksa benim uygulamam mı? (Haskell bana yedi mühürlü bir kitap olduğundan, ikincisi oldukça muhtemeldir.)

Hugs kullanıyor musunuz ? Hugs oldukça yavaş bir tercüman. Eğer kullanıyorsanız, belki GHC ile daha iyi bir zaman alabilirsiniz - ama ben sadece cogitating hipotezim, iyi bir Haskell derleyicisinin kaputun altında yaptığı şeyler oldukça büyüleyici ve anlayışımın çok ötesinde :)

Soru 3: Faktörleri belirleme şeklimizi değiştirmeden bu uygulamaları nasıl optimize edeceğime dair bazı ipuçları verebilir misiniz? Herhangi bir şekilde optimizasyon: daha güzel, daha hızlı, dile daha "yerli".

Sence şanssız bir oyun oynuyorsun. Çeşitli dilleri bilmenin en iyi yanı onları mümkün olan en farklı şekilde kullanmaktır :) Ama konuya giriyorum, sadece bu nokta için herhangi bir tavsiyem yok. Üzgünüm, umarım birisi bu durumda size yardımcı olabilir :)

Soru 4: İşlevsel uygulamalarım LCO'ya izin veriyor ve bu nedenle çağrı yığınına gereksiz kareler eklemekten kaçınıyor mu?

Hatırladığım kadarıyla, bir değeri döndürmeden önce yinelemeli çağrınızın son komut olduğundan emin olmanız gerekir. Başka bir deyişle, aşağıdaki gibi bir işlev bu optimizasyonu kullanabilir:

def factorial(n, acc=1):
    if n > 1:
        acc = acc * n
        n = n - 1
        return factorial(n, acc)
    else:
        return acc

Ancak, işleviniz aşağıdaki gibi olsaydı, böyle bir optimizasyon olmazdı, çünkü özyinelemeli çağrıdan sonra bir işlem (çarpma) var:

def factorial2(n):
    if n > 1:
        f = factorial2(n-1)
        return f*n
    else:
        return 1

Hangi işlemlerin yürütüldüğünü açıklamak için bazı yerel değişkenlerdeki işlemleri ayırdım. Bununla birlikte, en yaygın olanı bu işlevleri aşağıdaki gibi görmek, ancak yaptığım noktaya eşdeğerdir:

def factorial(n, acc=1):
    if n > 1:
        return factorial(n-1, acc*n)
    else:
        return acc

def factorial2(n):
    if n > 1:
        return n*factorial(n-1)
    else:
        return 1

Kuyruk özyineleme yapıp yapmayacağına karar vermek derleyiciye / yorumlayıcıya bağlıdır. Örneğin, iyi hatırlarsam Python yorumlayıcısı bunu yapmaz (örneğimde sadece akıcı sözdizimi nedeniyle Python'u kullandım). Eğer böyle iki parametre (ve parametrelerden biri gibi isimler vardır ile faktöryel fonksiyonları olarak garip şeyler bulmak Neyse, acc, accumulatorvs.) insanlar bunu neden şimdi biliyorsunuz :)


@ Hiperboreus teşekkür ederim! Ayrıca, sonraki sorularınızı gerçekten merak ediyorum. Ancak sizi her türlü sorunuza cevap veremediğim için bilgimin sınırlı olduğu konusunda uyarıyorum. Bunu telafi etmeye çalışmak için cevap topluluğumun wiki'sini yaptım, böylece insanlar daha kolay tamamlayabilirler.
brandizzi

Aralığı kullanma hakkında. Aralığı, bir while döngüsüyle artışla değiştirdiğimde (C döngüsünü taklit ederek), yürütme süresi aslında iki katına çıkar. Sanırım jeneratörler oldukça optimize edilmiş.
Hyperboreus

12

Haskell ile, özyinelemeleri açıkça düşünmenize gerek yoktur.

factorCount number = foldr factorCount' 0 [1..isquare] -
                     (fromEnum $ square == fromIntegral isquare)
    where
      square = sqrt $ fromIntegral number
      isquare = floor square
      factorCount' candidate
        | number `rem` candidate == 0 = (2 +)
        | otherwise = id

triangles :: [Int]
triangles = scanl1 (+) [1,2..]

main = print . head $ dropWhile ((< 1001) . factorCount) triangles

Yukarıdaki kodda, @Thomas'ın cevabındaki açık özyinelemeleri ortak liste işlemleri ile değiştirdim. Kod, kuyruk özyineleme konusunda endişelenmeden hala aynı şeyi yapıyor. GHC 7.6.2 ile makinemdeki @Thomas'ın cevabındaki (~ 7.04s ) versiyondan yaklaşık % 6 daha yavaş çalışır (~ 7.49s ) , @ Raedwulf'ın C versiyonu ~ 3.15s çalışır . Görünüşe göre GHC yıl boyunca iyileşti.

PS. Ben eski bir soru olduğunu biliyorum, ve ben google aramalardan tökezlemek (şimdi ne aradığını unuttum, şimdi ...). Sadece LCO ile ilgili soru üzerine yorum yapmak ve genel olarak Haskell hakkındaki duygularımı ifade etmek istedim. En iyi cevaba yorum yapmak istedim, ancak yorumlar kod bloklarına izin vermiyor.


9

C versiyonu için daha fazla sayı ve açıklama. Görünüşe göre hiç kimse bunu o yıllar boyunca yapmadı. Herkesin görmesi ve öğrenmesi için bu cevabı oylamayı unutmayın.

Birinci Adım: Yazarın programlarının karşılaştırması

Dizüstü Bilgisayar Özellikleri:

  • CPU i3 M380 (931 MHz - maksimum pil tasarrufu modu)
  • 4GB bellek
  • Win7 64 bit
  • Microsoft Visual Studio 2012 Ultimate
  • Gcc ile Cygwin 4.9.3
  • Python 2.7.10

Komutlar:

compiling on VS x64 command prompt > `for /f %f in ('dir /b *.c') do cl /O2 /Ot /Ox %f -o %f_x64_vs2012.exe`
compiling on cygwin with gcc x64   > `for f in ./*.c; do gcc -m64 -O3 $f -o ${f}_x64_gcc.exe ; done`
time (unix tools) using cygwin > `for f in ./*.exe; do  echo "----------"; echo $f ; time $f ; done`

.

----------
$ time python ./original.py

real    2m17.748s
user    2m15.783s
sys     0m0.093s
----------
$ time ./original_x86_vs2012.exe

real    0m8.377s
user    0m0.015s
sys     0m0.000s
----------
$ time ./original_x64_vs2012.exe

real    0m8.408s
user    0m0.000s
sys     0m0.015s
----------
$ time ./original_x64_gcc.exe

real    0m20.951s
user    0m20.732s
sys     0m0.030s

Dosya adları: integertype_architecture_compiler.exe

  • integertype şimdilik orijinal programla aynı (daha sonra daha fazla)
  • derleyici ayarlarına bağlı olarak mimari x86 veya x64
  • derleyici gcc veya vs2012

İkinci Adım: Tekrar Araştırın, Geliştirin ve Kıyaslayın

VS gcc'den% 250 daha hızlıdır. İki derleyici benzer bir hız vermelidir. Açıkçası, kod veya derleyici seçenekleriyle ilgili bir sorun var. Hadi araştıralım!

İlgilenilen ilk nokta tam sayı tipleridir. Dönüşümler pahalı olabilir ve daha iyi kod üretimi ve optimizasyonları için tutarlılık önemlidir. Tüm tamsayılar aynı türde olmalıdır.

Bu karışık bir karmaşa intve longşimdi. Bunu geliştireceğiz. Ne tür kullanılır? En hızlı. Hepsini kıyaslamalıyız!

----------
$ time ./int_x86_vs2012.exe

real    0m8.440s
user    0m0.016s
sys     0m0.015s
----------
$ time ./int_x64_vs2012.exe

real    0m8.408s
user    0m0.016s
sys     0m0.015s
----------
$ time ./int32_x86_vs2012.exe

real    0m8.408s
user    0m0.000s
sys     0m0.015s
----------
$ time ./int32_x64_vs2012.exe

real    0m8.362s
user    0m0.000s
sys     0m0.015s
----------
$ time ./int64_x86_vs2012.exe

real    0m18.112s
user    0m0.000s
sys     0m0.015s
----------
$ time ./int64_x64_vs2012.exe

real    0m18.611s
user    0m0.000s
sys     0m0.015s
----------
$ time ./long_x86_vs2012.exe

real    0m8.393s
user    0m0.015s
sys     0m0.000s
----------
$ time ./long_x64_vs2012.exe

real    0m8.440s
user    0m0.000s
sys     0m0.015s
----------
$ time ./uint32_x86_vs2012.exe

real    0m8.362s
user    0m0.000s
sys     0m0.015s
----------
$ time ./uint32_x64_vs2012.exe

real    0m8.393s
user    0m0.015s
sys     0m0.015s
----------
$ time ./uint64_x86_vs2012.exe

real    0m15.428s
user    0m0.000s
sys     0m0.015s
----------
$ time ./uint64_x64_vs2012.exe

real    0m15.725s
user    0m0.015s
sys     0m0.015s
----------
$ time ./int_x64_gcc.exe

real    0m8.531s
user    0m8.329s
sys     0m0.015s
----------
$ time ./int32_x64_gcc.exe

real    0m8.471s
user    0m8.345s
sys     0m0.000s
----------
$ time ./int64_x64_gcc.exe

real    0m20.264s
user    0m20.186s
sys     0m0.015s
----------
$ time ./long_x64_gcc.exe

real    0m20.935s
user    0m20.809s
sys     0m0.015s
----------
$ time ./uint32_x64_gcc.exe

real    0m8.393s
user    0m8.346s
sys     0m0.015s
----------
$ time ./uint64_x64_gcc.exe

real    0m16.973s
user    0m16.879s
sys     0m0.030s

Tamsayı tipleri int long int32_t uint32_t int64_tve uint64_tgelen#include <stdint.h>

C'de LOTS tamsayı türü vardır, ayrıca bazılarıyla oynamak için imzalı / imzasız, artı x86 veya x64 olarak derleme seçeneği (gerçek tamsayı boyutuyla karıştırılmamalıdır) vardır. Bu derlemek ve çalıştırmak için sürümleri bir sürü ^^

Üçüncü Adım: Sayıları Anlama

Kesin sonuçlar:

  • 32 bit tamsayı 64 bit eşdeğerinden ~% 200 daha hızlıdır
  • işaretsiz 64 bit tamsayı% 25 daha hızlı olan imzalı 64 bit (Ne yazık ki, bunun için bir açıklama var)

Hile sorusu: "C cinsinden int ve long boyutları nelerdir?"
Doğru cevap şudur: C'deki int ve long boyutları iyi tanımlanmamıştır!

C özelliğinden:

int en az 32 bit
uzunluğunda en az bir int

Gcc man sayfasından (-m32 ve -m64 bayrakları):

32-bit ortam, int, long ve pointer değerlerini 32 bit olarak ayarlar ve herhangi bir i386 sisteminde çalışan kod üretir.
64 bit ortam, int'i 32 bite, uzun ve işaretçiyi 64 bite ayarlar ve AMD'nin x86-64 mimarisi için kod üretir.

MSDN belgelerinden (Veri Türü Aralıkları) https://msdn.microsoft.com/en-us/library/s3f49ktz%28v=vs.110%29.aspx :

int, 4 bayt, aynı zamanda
uzun imzalı , 4 bayt, uzun int ve uzun imzalı

Sonuç olarak: Alınan Dersler

  • 32 bit tamsayıları 64 bit tamsayılarından daha hızlıdır.

  • Standart tamsayı türleri C veya C ++ 'da iyi tanımlanmamıştır, derleyicilere ve mimarilere bağlı olarak değişir. Tutarlılığa ve öngörülebilirliğe ihtiyacınız olduğunda, içinden uint32_ttamsayı ailesini kullanın #include <stdint.h>.

  • Hız sorunları çözüldü. Diğer tüm diller yüzde yüzler geride kaldı, C & C ++ tekrar kazandı! Hep yaparlar. Bir sonraki gelişme OpenMP: D kullanarak çoklu kullanım olacaktır.


Meraktan, Intel derleyicileri nasıl yapar? Genellikle sayısal kodu optimize etmekte gerçekten iyidirler.
kirbyfan64sos

Nerede C spec garanti "int en az 32 bit" olduğunu gösteren bir referans bulabilirsiniz? Tek bildiğim garantiler INT_MINve INT_MAX(-32767 ve 32767'nin asgari büyüklükleridir) (pratik intolarak en az 16 bitlik bir gereksinim getiren ). longen az bir kadar büyük olmalıdır intve aralık gereklilikleri ortalaması longen az 32 bittir.
ShadowRanger


8

Erlang uygulamanıza bakın. Zamanlama, tüm sanal makinenin başlatılmasını, programınızı çalıştırmasını ve sanal makineyi durdurmayı içerir. Erlang vm'yi kurmanın ve durdurmanın biraz zaman alacağından eminim.

Zamanlama, erlang sanal makinesinin içinde yapıldıysa, sonuçlar sadece bu durumda yalnızca söz konusu program için gerçek zamanımız olacağından farklı olacaktır. Aksi takdirde, Erlang Vm'nin başlatılması ve yüklenmesi işleminin ve onu durdurmanın (programınıza koyduğunuz gibi) toplam süresinin, zaman için kullandığınız yöntemin toplam süresine dahil olduğuna inanıyorum. program çıktı. Programlarımızı sanal makinenin içinde zamanlamak istediğimizde kullandığımız erlang zamanlamasının kendisini kullanmayı düşünün timer:tc/1 or timer:tc/2 or timer:tc/3. Bu şekilde, erlang sonuçları sanal makineyi başlatmak ve durdurmak / öldürmek / durdurmak için harcanan zamanı hariç tutacaktır. Bu benim akıl yürütmem, bunu düşünün ve daha sonra bench mark'ınızı tekrar deneyin.

Aslında kesin bir değer elde etmek için programı (bir çalışma zamanı olan diller için), bu dillerin çalışma zamanı içinde zamanlamaya çalışmanızı öneririz. Örneğin C, Erlang, Python ve Haskell gibi bir çalışma zamanı sistemini başlatma ve kapatma yükü yoktur (bundan% 98 emin - i düzeltme). Yani (bu muhakemeye dayanarak) bu kriterin bir çalışma zamanı sisteminin üstünde çalışan diller için yeterince hassas / adil olmadığını söyleyerek sonuca varıyorum. Bu değişikliklerle tekrar yapalım.

EDIT: Tüm diller çalışma zamanı sistemlerine sahip olsa bile, her birini başlatma ve durdurma yükü farklı olacaktır. bu yüzden çalışma zamanı sistemlerinden zaman öneriyoruz (bunun geçerli olduğu diller için). Erlang VM'nin başlangıçta önemli ölçüde ek yükü olduğu biliniyor!


Yazımda bahsetmeyi unuttum, ancak sistemi başlatmak için gereken süreyi ölçtüm (erl -noshell -s erlang durt) - makinemde yaklaşık 0.1 saniye. Bu, programın çalışma süresine (yaklaşık 10 saniye) kıyasla tartışmaya değmeyecek kadar küçüktür.
RichardC

makinenizde! güneş yangın sunucusu üzerinde çalışıp çalışmadığınızı bilmiyoruz !. Zaman, makine teknik özellikleriyle orantılı bir değişken olduğundan, dikkate alınmalıdır ....
Muzaaya Joshua

2
@RichardC Hiçbir yerde Erlang'ın daha hızlı olduğunu söylemedi :) Farklı hedefleri var, hız değil!
istisna

7

Soru 1: Erlang, Python ve Haskell, rastgele uzunluk tamsayıları nedeniyle hız kaybediyor mu veya değerler MAXINT'den az olduğu sürece mi?

Birinci soru Erlang için olumsuz olarak cevaplanabilir. Son soru şu şekilde Erlang kullanılarak uygun şekilde cevaplanır:

http://bredsaal.dk/learning-erlang-using-projecteuler-net

İlk C örneğinizden daha hızlı olduğundan, diğerlerinin zaten ayrıntılı olarak ele aldığı gibi sayısız sorun olduğunu tahmin ediyorum.

Bu Erlang modülü yaklaşık 5 saniye içinde ucuz bir netbook üzerinde çalışır ... Erlang'daki network iş parçacığı modelini kullanır ve bu nedenle olay modelinden nasıl faydalanılacağını gösterir. Birçok düğüm üzerinde dağıtılabilir. Ve hızlı. Kodum değil.

-module(p12dist).  
-author("Jannich Brendle, jannich@bredsaal.dk, http://blog.bredsaal.dk").  
-compile(export_all).

server() ->  
  server(1).

server(Number) ->  
  receive {getwork, Worker_PID} -> Worker_PID ! {work,Number,Number+100},  
  server(Number+101);  
  {result,T} -> io:format("The result is: \~w.\~n", [T]);  
  _ -> server(Number)  
  end.

worker(Server_PID) ->  
  Server_PID ! {getwork, self()},  
  receive {work,Start,End} -> solve(Start,End,Server_PID)  
  end,  
  worker(Server_PID).

start() ->  
  Server_PID = spawn(p12dist, server, []),  
  spawn(p12dist, worker, [Server_PID]),  
  spawn(p12dist, worker, [Server_PID]),  
  spawn(p12dist, worker, [Server_PID]),  
  spawn(p12dist, worker, [Server_PID]).

solve(N,End,_) when N =:= End -> no_solution;

solve(N,End,Server_PID) ->  
  T=round(N*(N+1)/2),
  case (divisor(T,round(math:sqrt(T))) > 500) of  
    true ->  
      Server_PID ! {result,T};  
    false ->  
      solve(N+1,End,Server_PID)  
  end.

divisors(N) ->  
  divisor(N,round(math:sqrt(N))).

divisor(_,0) -> 1;  
divisor(N,I) ->  
  case (N rem I) =:= 0 of  
  true ->  
    2+divisor(N,I-1);  
  false ->  
    divisor(N,I-1)  
  end.

Aşağıdaki test bir: Intel (R) Atom (TM) CPU N270 @ 1.60GHz'de gerçekleştirildi.

~$ time erl -noshell -s p12dist start

The result is: 76576500.

^C

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
a

real    0m5.510s
user    0m5.836s
sys 0m0.152s

değeri aşağıdaki gibi 1000'e çıkarmak doğru sonucu elde etmez. Yukarıdaki gibi> 500 ile, en yeni test: IntelCore2 CPU 6600 @ 2.40GHz gerçek 0m2.370s'de tamamlandı
Mark Washeim

sonucunuz: 76576500 herkes: 842161320 sonucunuzla ilgili yanlış bir şey var
davidDavidson

Diğer Euler problemlerinden dong olduğum için, sonucumu kontrol ettim. Cevabı projecteuler.net/problem=12 76576500 Bu konuda hiçbir soru. Garip göründüğünü biliyorum, ama kontrol ettim.
Mark Washeim

Karşılaştırma için Mark'ın kodu ile Erlang 19 kullanırken 9.03 orijinal c sürümü ile alıyorum 5.406, 167.0366% daha hızlı alıyorum.
thanos

5

C ++ 11, benim için <20 ms - Burada çalıştırın

Dile özgü bilginizi geliştirmeye yardımcı olacak ipuçları istediğinizi anlıyorum, ancak burada iyi ele alındığı için, sorunuzun matematiksel yorumuna bakmış olabilecek insanlar için bir bağlam ekleyeceğimi düşündüm ve bunun nedenini merak ettim. kod çok daha yavaştı.

Bu cevap, temel olarak insanların sorunuzun / diğer yanıtların kodunu daha kolay değerlendirmesine yardımcı olacak bir bağlam sağlamaktır.

Bu kod, aşağıdakiler temelinde kullanılan dil ile ilgili olmayan yalnızca birkaç (çirkin) optimizasyon kullanır:

  1. her traingle numarası n (n + 1) / 2 biçimindedir
  2. n ve n + 1 eşzamanlıdır
  3. bölen sayısı çarpımsal bir fonksiyondur

#include <iostream>
#include <cmath>
#include <tuple>
#include <chrono>

using namespace std;

// Calculates the divisors of an integer by determining its prime factorisation.

int get_divisors(long long n)
{
    int divisors_count = 1;

    for(long long i = 2;
        i <= sqrt(n);
        /* empty */)
    {
        int divisions = 0;
        while(n % i == 0)
        {
            n /= i;
            divisions++;
        }

        divisors_count *= (divisions + 1);

        //here, we try to iterate more efficiently by skipping
        //obvious non-primes like 4, 6, etc
        if(i == 2)
            i++;
        else
            i += 2;
    }

    if(n != 1) //n is a prime
        return divisors_count * 2;
    else
        return divisors_count;
}

long long euler12()
{
    //n and n + 1
    long long n, n_p_1;

    n = 1; n_p_1 = 2;

    // divisors_x will store either the divisors of x or x/2
    // (the later iff x is divisible by two)
    long long divisors_n = 1;
    long long divisors_n_p_1 = 2;

    for(;;)
    {
        /* This loop has been unwound, so two iterations are completed at a time
         * n and n + 1 have no prime factors in common and therefore we can
         * calculate their divisors separately
         */

        long long total_divisors;                 //the divisors of the triangle number
                                                  // n(n+1)/2

        //the first (unwound) iteration

        divisors_n_p_1 = get_divisors(n_p_1 / 2); //here n+1 is even and we

        total_divisors =
                  divisors_n
                * divisors_n_p_1;

        if(total_divisors > 1000)
            break;

        //move n and n+1 forward
        n = n_p_1;
        n_p_1 = n + 1;

        //fix the divisors
        divisors_n = divisors_n_p_1;
        divisors_n_p_1 = get_divisors(n_p_1);   //n_p_1 is now odd!

        //now the second (unwound) iteration

        total_divisors =
                  divisors_n
                * divisors_n_p_1;

        if(total_divisors > 1000)
            break;

        //move n and n+1 forward
        n = n_p_1;
        n_p_1 = n + 1;

        //fix the divisors
        divisors_n = divisors_n_p_1;
        divisors_n_p_1 = get_divisors(n_p_1 / 2);   //n_p_1 is now even!
    }

    return (n * n_p_1) / 2;
}

int main()
{
    for(int i = 0; i < 1000; i++)
    {
        using namespace std::chrono;
        auto start = high_resolution_clock::now();
        auto result = euler12();
        auto end = high_resolution_clock::now();

        double time_elapsed = duration_cast<milliseconds>(end - start).count();

        cout << result << " " << time_elapsed << '\n';
    }
    return 0;
}

Bu, masaüstüm için ortalama 19ms ve dizüstü bilgisayarım için 80ms, burada gördüğüm diğer kodların çoğundan çok ağlıyor. Ve şüphe yok ki, hala birçok optimizasyon var.


7
Bu oldukça açık bir şekilde, “Gerçekten dört dilde mümkün olduğunca aynı algoritmayı uygulamaya çalıştım” dedi. Sizinkine benzer birçok silinen cevaptan birine yorum yapmak için "dilden bağımsız olarak daha iyi bir algoritma ile daha yüksek hızlar elde edebileceğiniz oldukça açıktır."
Thomas M. DuBuisson

2
@ ThomasM.DuBuisson. Ben de buna katılıyorum. \ Cevapları sorusu, algoritmik hız artışlarının önemli olduğunu ima ediyor (ve elbette OP onlardan istemiyor), ancak açık bir örnek yok. Tam olarak optimize edilmiş kod olmayan bu cevap, OP kodunun ne kadar yavaş / hızlı olduğunu merak eden herkes için biraz yararlı bir bağlam sağladığını düşünüyorum.
user3125280

gcc birçok paterni önceden hesaplayabilir. int a = 0; (int i = 0; i <10000000; ++ i) {a + = i;} derleme zamanında hesaplanacağı için çalışma zamanında <1ms alır. Çok önemli
Arthur

@Thomas: user3125280 ile aynı fikirdeyim - diller, aptalca bir şey yaparken gerçek bir programlama dilini nasıl yenemedikleri yerine akıllı bir şey yapmak için nasıl ücretlendirildikleri karşılaştırılmalıdır . Akıllı algoritmalar genellikle mikroskopik verimlilik, esneklik, işleri bağlama (birleştirme) ve altyapıdan daha az önem verir. Biri 20 ms veya 50 ms olsun, 8 saniye veya 8 dakika almıyor .
DarthGizka

5

GO çalışıyor:

package main

import "fmt"
import "math"

func main() {
    var n, m, c int
    for i := 1; ; i++ {
        n, m, c = i * (i + 1) / 2, int(math.Sqrt(float64(n))), 0
        for f := 1; f < m; f++ {
            if n % f == 0 { c++ }
    }
    c *= 2
    if m * m == n { c ++ }
    if c > 1001 {
        fmt.Println(n)
        break
        }
    }
}

Alırım:

orijinal c versiyonu: 9.1690 % 100
git: 8.2520 % 111

Ancak şunu kullanarak:

package main

import (
    "math"
    "fmt"
 )

// Sieve of Eratosthenes
func PrimesBelow(limit int) []int {
    switch {
        case limit < 2:
            return []int{}
        case limit == 2:
            return []int{2}
    }
    sievebound := (limit - 1) / 2
    sieve := make([]bool, sievebound+1)
    crosslimit := int(math.Sqrt(float64(limit))-1) / 2
    for i := 1; i <= crosslimit; i++ {
        if !sieve[i] {
            for j := 2 * i * (i + 1); j <= sievebound; j += 2*i + 1 {
                sieve[j] = true
            }
        }
    }
    plimit := int(1.3*float64(limit)) / int(math.Log(float64(limit)))
    primes := make([]int, plimit)
    p := 1
    primes[0] = 2
    for i := 1; i <= sievebound; i++ {
        if !sieve[i] {
            primes[p] = 2*i + 1
            p++
            if p >= plimit {
                break
            }
        }
    }
    last := len(primes) - 1
    for i := last; i > 0; i-- {
        if primes[i] != 0 {
            break
        }
        last = i
    }
    return primes[0:last]
}



func main() {
    fmt.Println(p12())
}
// Requires PrimesBelow from utils.go
func p12() int {
    n, dn, cnt := 3, 2, 0
    primearray := PrimesBelow(1000000)
    for cnt <= 1001 {
        n++
        n1 := n
        if n1%2 == 0 {
            n1 /= 2
        }
        dn1 := 1
        for i := 0; i < len(primearray); i++ {
            if primearray[i]*primearray[i] > n1 {
                dn1 *= 2
                break
            }
            exponent := 1
            for n1%primearray[i] == 0 {
                exponent++
                n1 /= primearray[i]
            }
            if exponent > 1 {
                dn1 *= exponent
            }
            if n1 == 1 {
                break
            }
        }
        cnt = dn * dn1
        dn = dn1
    }
    return n * (n - 1) / 2
}

Alırım:

orijinal c versiyonu: 9.1690 100%
thaumkid'in c versiyonu: 0.1060 8650%
ilk gitmek sürümü: 8.2520 111%
ikinci gitmek sürüm: 0.0230 39865%

Ayrıca Python3.6 ve pypy3.3-5.5-alpha'yı denedim:

orijinal c versiyonu: 8.629 100%
thaumkid'in c versiyonu: 0.109 7916%
Python3.6: 54.795 16%
pypy3.3-5.5-alpha: 13.291 65%

ve sonra aşağıdaki kod ile aldım:

orijinal c versiyonu: 8.629 % 100
thaumkid'in c versiyonu: 0.109 8650%
Python3.6: 1.489 580%
pypy3.3-5.5-alpha: 0.582 1483%

def D(N):
    if N == 1: return 1
    sqrtN = int(N ** 0.5)
    nf = 1
    for d in range(2, sqrtN + 1):
        if N % d == 0:
            nf = nf + 1
    return 2 * nf - (1 if sqrtN**2 == N else 0)

L = 1000
Dt, n = 0, 0

while Dt <= L:
    t = n * (n + 1) // 2
    Dt = D(n/2)*D(n+1) if n%2 == 0 else D(n)*D((n+1)/2)
    n = n + 1

print (t)

1

Değişiklik: case (divisor(T,round(math:sqrt(T))) > 500) of

Kime: case (divisor(T,round(math:sqrt(T))) > 1000) of

Bu, Erlang çoklu işlem örneği için doğru cevabı verecektir.


2
Bu, bu cevaba bir yorum olarak mı tasarlandı ? Çünkü net değil ve bu kendi başına bir cevap değil.
ShadowRanger

1

Faktör sayısının ancak ilgili sayıların çok sayıda küçük faktörü varsa büyük olduğunu varsaydım. Bu yüzden thaumkid'in mükemmel algoritmasını kullandım, ancak önce asla çok küçük olmayan faktör sayısına bir yaklaşım kullandım. Oldukça basit: 29'a kadar asal faktörleri kontrol edin, ardından kalan sayıyı kontrol edin ve faktörlerin üst sınırı için bir üst sınır hesaplayın. Faktör sayısı için bir üst sınır hesaplamak için bunu kullanın ve bu sayı yeterince yüksekse, faktörlerin tam sayısını hesaplayın.

Aşağıdaki kod, doğruluk için bu varsayımı gerektirmez, ancak hızlı olmak için. İşe yarıyor gibi görünüyor; 100.000 rakamdan yalnızca biri, tam kontrol gerektirecek kadar yüksek bir tahmin verir.

İşte kod:

// Return at least the number of factors of n.
static uint64_t approxfactorcount (uint64_t n)
{
    uint64_t count = 1, add;

#define CHECK(d)                            \
    do {                                    \
        if (n % d == 0) {                   \
            add = count;                    \
            do { n /= d; count += add; }    \
            while (n % d == 0);             \
        }                                   \
    } while (0)

    CHECK ( 2); CHECK ( 3); CHECK ( 5); CHECK ( 7); CHECK (11); CHECK (13);
    CHECK (17); CHECK (19); CHECK (23); CHECK (29);
    if (n == 1) return count;
    if (n < 1ull * 31 * 31) return count * 2;
    if (n < 1ull * 31 * 31 * 37) return count * 4;
    if (n < 1ull * 31 * 31 * 37 * 37) return count * 8;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41) return count * 16;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43) return count * 32;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47) return count * 64;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53) return count * 128;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59) return count * 256;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61) return count * 512;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67) return count * 1024;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67 * 71) return count * 2048;
    if (n < 1ull * 31 * 31 * 37 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67 * 71 * 73) return count * 4096;
    return count * 1000000;
}

// Return the number of factors of n.
static uint64_t factorcount (uint64_t n)
{
    uint64_t count = 1, add;

    CHECK (2); CHECK (3);

    uint64_t d = 5, inc = 2;
    for (; d*d <= n; d += inc, inc = (6 - inc))
        CHECK (d);

    if (n > 1) count *= 2; // n must be a prime number
    return count;
}

// Prints triangular numbers with record numbers of factors.
static void printrecordnumbers (uint64_t limit)
{
    uint64_t record = 30000;

    uint64_t count1, factor1;
    uint64_t count2 = 1, factor2 = 1;

    for (uint64_t n = 1; n <= limit; ++n)
    {
        factor1 = factor2;
        count1 = count2;

        factor2 = n + 1; if (factor2 % 2 == 0) factor2 /= 2;
        count2 = approxfactorcount (factor2);

        if (count1 * count2 > record)
        {
            uint64_t factors = factorcount (factor1) * factorcount (factor2);
            if (factors > record)
            {
                printf ("%lluth triangular number = %llu has %llu factors\n", n, factor1 * factor2, factors);
                record = factors;
            }
        }
    }
}

Bu, yaklaşık 0,7 saniyede 13824 faktörlü 14,753,024 üçgen, 34 saniyede 61,440 faktörlü 879,207,615 üçgen sayı ve 10 saniyede 138,240 faktörlü 12,524,486,975 üçgen sayı ve 172,032 faktörlü 26,467,792,064 üçgen sayı bulur. 21 dakika 25 saniye (2.4GHz Core2 Duo), bu nedenle bu kod ortalama olarak sayı başına yalnızca 116 işlemci çevrimi alır. Son üçgen sayının kendisi 2 ^ 68'den büyük, bu yüzden


0

"Jannich Brendle" sürümünü 500 yerine 1000 olarak değiştirdim. Ve euler12.bin, euler12.erl, p12dist.erl sonuçlarını listeledim. Her iki erl kodu da derlemek için '+ native' kullanır.

zhengs-MacBook-Pro:workspace zhengzhibin$ time erl -noshell -s p12dist start
The result is: 842161320.

real    0m3.879s
user    0m14.553s
sys     0m0.314s
zhengs-MacBook-Pro:workspace zhengzhibin$ time erl -noshell -s euler12 solve
842161320

real    0m10.125s
user    0m10.078s
sys     0m0.046s
zhengs-MacBook-Pro:workspace zhengzhibin$ time ./euler12.bin 
842161320

real    0m5.370s
user    0m5.328s
sys     0m0.004s
zhengs-MacBook-Pro:workspace zhengzhibin$

0
#include <stdio.h>
#include <math.h>

int factorCount (long n)
{
    double square = sqrt (n);
    int isquare = (int) square+1;
    long candidate = 2;
    int count = 1;
    while(candidate <= isquare && candidate<=n){
        int c = 1;
        while (n % candidate == 0) {
           c++;
           n /= candidate;
        }
        count *= c;
        candidate++;
    }
    return count;
}

int main ()
{
    long triangle = 1;
    int index = 1;
    while (factorCount (triangle) < 1001)
    {
        index ++;
        triangle += index;
    }
    printf ("%ld\n", triangle);
}

gcc -lm -Ofast euler.c

zaman ./a.out

2.79s kullanıcı 0.00s sistemi 99% işlemci 2.794 toplam

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.