שלום אורח התחבר

השוואת זמני ריצה בין השפות פרל, פייתון ורובי

נושאים:perlC++rubypython

פוסט זה כולל טיפ קצר לעבודה יעילה עם Python. אם אתם רוצים ללמוד פייתון יותר לעומק אני ממליץ על קורס Python כאן באתר.
הקורס כולל עשרות שיעורי וידאו והמון תרגול מעשי וילמד אתכם Python בצורה מקצועית מההתחלה ועד הנושאים המתקדמים.

בעולם שפות התכנות יש הפרדה (לפחות תפיסתית) בין שפות ״אמיתיות״ לשפות ״סקריפטים״. בשפה אמיתית תשתמשו לכתוב Kernel או לפתח קוד תקשורת זמן-אמת ואילו בשפת הסקריפטים נשתמש בשביל לכתוב דברים שזמן הביצוע שלהם פחות חשוב.
בשנים האחרונות חל מהפך תפיסתי מסוים כשמערכות ווב עוברות להכתב בשפות הקלילות. הסיבה היא בעיקר שיפור בחומרה שאיפשר לשפות הסקריפטים לרוץ מהר מספיק.

בפוסט זה אני לא הולך לבחור את האבחנה בין שני הסוגים, או לנסות להבין איזה שפה שייכת לאיזה קטגוריה. במקום, לקחתי 3 שפות השייכות בבירור לקטגוריית השפות הקלילות ובחרתי לראות מי משלושתן מבצעת את אותה המשימה מהר יותר. 

1המשימה

בשביל לעשות חיים קשים למחשב ולא יותר מדי קשים עבורנו, נכתוב תוכנית המבצעת חישוב מקדים לערכי MD5 של מחרוזות בשפה. ישנן מעל 1.5 מיליון מחרוזות באורך 4 המורכבות מאותיות קטנות וספרות מה שאומר שהחישוב ייקח זמן אבל לא יותר מדי.
בשביל לסבך עוד יותר את המחשב, שמרתי את כל התוצאות בזכרון במבנה של Hash ובסוף שלפתי ממנו את אחד הערכים.

התוכנית מומשה באופן מכוון בצורה מאוד דומה בין השפות. 

2נתחיל עם פרל

פרל היתה ההפתעה הגדולה שלי במבחן, מאחר ולפחות בחוגים בהם אני מסתובב היא ידועה בתור המהירה בחבורה. ייתכן והבעייה במודול המחשב MD5 או במקום אחר. גירסת פרל ששימשה לניסוי היא 5.20. בכל מקרה הנה התוכנית וזמן העבודה על הנייד שלי:

use strict;
use warnings;
use v5.18;
use Digest::MD5 qw/md5_hex/;
use File::Slurp;

my $len = 4;
my @ab = (0..9, 'a'..'z');
my $total = @ab ** $len;

my %hash;

for ( my $i= 0; $i < $total; $i++ ) {
	my $next = join('', map { char_at(\@ab, $i, $_) } (0..$len - 1));
	my $word = $next;
	my $md5 = md5_hex($next);
	if ( $i % 100_000 == 0 ) {
		say int($i/100_000), " / ", int($total/100_000);
	}
	$hash{$md5} = $word;
}
say $hash{"657f8b8da628ef83cf69101b6817150a"};


sub char_at {
	my ($ab, $i, $d) = @_;
	$ab->[($i / (@$ab ** $d )) % @$ab ];
}

וזמן הריצה:

real 0m14.610s
user 0m13.874s
sys 0m0.445s

3נמשיך עם פייתון

פייתון הציגה פער ניכר מפרל בזמן החישוב. הגירסא היא 2.7.6 והנה הקוד והתוצאה:

import string
import hashlib

def main():
    _len = 4
    _trace = 100000

    ab = string.ascii_lowercase + string.digits
    total = len(ab) ** _len

    print "total = %d" % total

    res = {}

    for i in xrange(total):
        m = hashlib.md5()
        _next = ''.join([char_at(ab, i, d) for d in range(_len)])
        m.update(_next)
        md5 = m.hexdigest()
        res[md5] = _next

        if i % _trace == 0:
            print "%d / %d" % ( int(i/_trace), int(total / _trace))

    print res["657f8b8da628ef83cf69101b6817150a"]

def char_at(ab, i, d):
    return ab[int(i / (len(ab) ** d)) % len(ab)]



if __name__ == '__main__':
    main()

זמן ריצה:

real 0m12.449s
user 0m11.578s
sys 0m0.498s

 

4והנצחון הולך ל… רובי

רובי הצליחה להציג פער קטן מאוד אך עקבי מול פייתון. אני משתמש בגירסת 2.1.2 והנה הקוד והתוצאה:

require 'openssl'

def char_at(ab, i, d)
	ab[(i / (ab.length ** d)).to_i % ab.length]
end

len = 4
ab = [*'0'..'9', *'a'..'z']

total = ab.length ** len
trace = 100_000
res = {}

hasher = OpenSSL::Digest::MD5.new

total.times do |idx|
	word = (0..len - 1).map { |d| char_at(ab, idx, d) }.join
	md5 = hasher.hexdigest(word)
	res[md5] = word

	puts "#{(idx/trace).to_i} / #{(total / trace).to_i}" if idx % trace == 0
end


puts res['657f8b8da628ef83cf69101b6817150a']

וזמן הריצה

real 0m12.098s
user 0m11.292s
sys 0m0.494s

 

5ורק לצורך ההשוואה: כך זה נראה ב C++

לא סתם מספרים ש C++ היא בחירה טובה כשביצועים הם שיקול. אותו הקוד רץ כמעט במחצית הזמן:

int main(int argc, char *argv[])
{    
    QCoreApplication a(argc, argv);

    QCryptographicHash hasher(QCryptographicHash::Md5);
    StringHash results;

    for ( size_t i=0; i < qPow(sz, len); i++ ) {
        QString next;

        for ( size_t d=0; d < len; d++ ) {
            next += ab[int(i / qPow(sz, d)) % sz];
        }
        if ( i % trace == 0 ) {
            qDebug() << int(i / trace) << " / " << int(total / trace);
        }

        hasher.addData(next.toLocal8Bit());
        QString md5 = QString(hasher.result().toHex());
        QString word = next;

        results[md5] = word;
        hasher.reset();
    }
    qDebug() << results["657f8b8da628ef83cf69101b6817150a"];

    return 0;
}

וזמן הריצה:

real	0m7.298s
user	0m6.816s
sys	0m0.368s

 

6השורה התחתונה

מעבר לטריוויה אני כלל לא בטוח שיש חשיבות לפערים אלו בין פרל, פייתון ורובי (וזה כמובן לא קשור לעובדה שפרל הפסידה כאן). מה שכן מעניין לראות זה הפער בין השפות הקלילות ל C++, שעוזר להבין את המשיכה של שפה זו. צריך גם לזכור שאת שיפור הביצועים המשמעותי אנו נקבל באמצעות מיקבול החישוב, אבל זה כבר נושא לפוסט אחר.

מעדיפים לקרוא מהטלגרם? בקרו אותנו ב:@tocodeil

או הזינו את כתובת המייל וקבלו את הפוסט היומי בכל בוקר אליכם לתיבה:


נהניתם מהפוסט? מוזמנים לשתף ולהגיב