Ubuntu Suomen keskustelualueet
Ubuntun käyttö => Ohjelmointi, palvelimet ja muu edistyneempi käyttö => Aiheen aloitti: SuperOscar - 18.05.12 - klo:21.38
-
Tuli tutun kanssa kinaa siitä, kumpi on tietyssä kumpaakin kiinnostavassa käytössä nopeampi, hänen suosikkinsa Perl vai minun suosikkini Python. Päätettiin kokeilla.
Rakensin 396 Mt tekstitiedoston. Sen jokaisella rivillä on satunnaisesti 1–80 merkkiä pelkkiä a-kirjaimia. Sitten tehtiin sekä Perlillä että Pythonilla skripti, joka muuttaa säännöllisiä lausekkeita käyttäen jokaisen a-kirjaimen jonoksi ”ööön”. Data luetaan vakiosyötevirrasta. Sitä ei kirjoiteta minnekään, koska tarkoitus oli mitoittaa vain sitä nopeutta, millä dataa luetaan ja muutetaan, ei esim. konsolin vieritysnopeutta.
Kolmiytimisellä pöytäkoneellani konsolitilassa (ei graafisia kilkkeitä takana käynnissä) tulokset olivat time-komennolla mitattuna:
Perl (versio 5.14.2): 2 min 45,29 s
Python (versio 3.2.3): 1 min 12,95 s
Pythonistina uskon kyllä vakaasti, että tuloksen pitikin mennä näin päin, mutta kaveri ei anna periksi enkä minäkään osannut kuvitella ihan noin suurta eroa – onhan Perlkin tunnettu nopeudestaan.
Osaisiko joku sanoa, mikä näissä koodeissa on vialla, ja millainen koe olisi rehellisempi?
Perl-koodi:
#!/usr/bin/perl
while (<>) {
s/a/ööön/g;
}
Python-koodi:
#!/usr/bin/env python
from sys import stdin
import re
m = re.compile('a')
for line in stdin:
m.sub('ööön', line)
-
Itseäni alkoi kiinnostaa myös minkä tuloksen saisi "sed": in avulla tai pelkällä "bash": llä.
-
Itseäni alkoi kiinnostaa myös minkä tuloksen saisi "sed": in avulla tai pelkällä "bash": llä.
sedillä ja bashilla tulos luultavasti olisi pakko kirjoittaa jonnekin. Sinänsä se ei haittaisi, jos panisi Perlin ja Pythoninkin kirjoittamaan tuloksen tiedostoon, jolloin testit olisivat keskenään samat.
-
sedillä ja bashilla tulos luultavasti olisi pakko kirjoittaa jonnekin.
Eikös hyvä paikka tulosteille olisi /dev/null jos ei halua tallettaa minnekään ?
sed 's/a/öön/g' ./koe.txt >/dev/null
-
Okei, kirjoitetaan /dev/nulliin eli uudet Perl- ja Python-koodit ovat:
#!/usr/bin/perl
while (<>) {
s/a/ööön/g;
print $_;
}
ja:
#!/usr/bin/env python
from sys import stdin
import re
m = re.compile('a')
for line in stdin:
print(m.sub('ööön', line))
(Huom: Python ei tiputa rivinvaihtoa eli se kirjoittaa itse asiassa /dev/nulliin tyhjän rivin jokaisen rivin jälkeen.)
Tulokset:
sed – 1 min 50,91 s
Python – 1 min 46,19 s
Perl – 2 min 49,93 s
Onko siis vain pakko uskoa, että Python on pirullisen nopea?
-
Nopeinhan se tuossa oli joskin marginaalisesti sed: iin verrattuna. Itse käytän mieluusti sed: iä tuon tyyppiseen "etsi/korvaa" tehtävään, lyhyt kirjoittaa.
-
Nopeinhan se tuossa oli joskin marginaalisesti sed: iin verrattuna. Itse käytän mieluusti sed: iä tuon tyyppiseen "etsi/korvaa" tehtävään, lyhyt kirjoittaa.
Joo, ilman muuta, jos kyse olisi pelkästään näin yksinkertaisesta muutoksesta, niin sediähän silloin kannattaisi käyttää.
Kiistassa on kuitenkin kyse sellaisen web-käyttöliittymäisen tietokannan käsittelystä, jossa tiedot ovat levyllä XML-muodossa ja niistä pitää mahdollisimman nopeasti pyöräyttää HTML:ää.
-
Innostua itsekkin testaamaan:
Python
$ time ./test.py <datafile.txt >/dev/null
real 0m7.908s
user 0m7.852s
sys 0m0.040s
Perl
$ time ./test.pe <datafile.txt >/dev/null
real 0m16.614s
user 0m16.541s
sys 0m0.036s
Java (Oracle Java 1.7.0_04 64bit)
$ time java -cp build/ ReplaceTest >/dev/null
real 0m12.723s
user 0m13.101s
sys 0m0.628s
Täältä löytyy Java lähdekoodi + datafile mitä käytin: http://corei7.no-ip.biz/~petria/replaceTest/
-
Täältä löytyy Java lähdekoodi + datafile mitä käytin: http://corei7.no-ip.biz/~petria/replaceTest/
En oikeastaan osaa Javaa ollenkaan, mutta käsittääkseni testisi ei ole täysin sama. Koodin rivi:
rplc = line.replaceAll("a", "öön");
...vastaa kai pikemmin Pythonin str.replace-metodia, joka korvaa staattisen alijonon? Tarkoitus oli käyttää säännöllisiä lausekkeita, vaikkei niiden käytöstä tässä mitään hyötyä olekaan.
-
Täältä löytyy Java lähdekoodi + datafile mitä käytin: http://corei7.no-ip.biz/~petria/replaceTest/
En oikeastaan osaa Javaa ollenkaan, mutta käsittääkseni testisi ei ole täysin sama. Koodin rivi:
rplc = line.replaceAll("a", "öön");
...vastaa kai pikemmin Pythonin str.replace-metodia, joka korvaa staattisen alijonon? Tarkoitus oli käyttää säännöllisiä lausekkeita, vaikkei niiden käytöstä tässä mitään hyötyä olekaan.
Eka parametri on regexp:
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#replaceAll(java.lang.String,%20java.lang.String)
-
Eka parametri on regexp:
OK, my bad.
-
Tämä ei toimi läheskään niin kuin vaatimus oli; muutenkin eroaa mutta nimenomaan se muuttaa annetun tiedoston; taitaa Pythonkin olla hidas kun pitää kirjoittaa levylle?
Toiminta ei silti ole ratkaisevasti hitaampi kuin Pythonilla. Mutta sovellettuna matriisille taitaisi nopeus olla verraton - minun taitoni ei kylläkään muutokseen riitä. Koodi on seuraava:
ed -s ~/koeteksti <<< $',s/a/ööön/g\nw'
- kyllä toiminta nopeutuu RAM-diskilläkin ehdottomaksi ykköseksi. Mutta ram-diskin muodostaminen vaatii sudo:a joten taitaa jäädä muistoksi vain. Muuten sieltä RAM-diskiltä voi käydä lukemassa.
- tulee kammottavan kokoinen tiedosto.
- ajanmittaus time:llä kertoo että vielä olisi paljon parantamisenvaraa mikäli osaisi.
-
Pikainen PHP versio. En tosin testannut ja lienekö tuossa eroa talletetaanko tulos eri bufferiin vai käytetäänkö koko ajan $c1 muuttujaa...
<?php
$c1 = file_get_contents("datafile.txt");
$c2 = str_replace("a", "ööön", $c1);
file_put_contents("/dev/null", $c2);
?>
...ja tuosta tuleekin mieleen, että millainenkohan ero on file_get_contents() V fopen()+fread().
Pitänee vääntää tuo Ansi C:llä! Se kertonee sen optimin pohjatuloksen, ellei nyt sitten suoraan aleta ASMlla virittämään.
-
Pitänee vääntää tuo Ansi C:llä!
ANSI C:ssä ei ole säännöllisiä lausekkeita, mutta jo POSIX C:ssä olisi. Sama kävi mielessä, mutta näin matalan tason toteutuksessa on myös enemmän mietittävää: miten tiedostoa luetaan? rajoitetaanko merkkijonon kokoa, koska jo valmiiksi tiedetään rivin enimmäispituuden olevan 80 merkkiä, vai varaudutaanko rajattoman pitkiin riveihin?
-
Pitänee vääntää tuo Ansi C:llä!
ANSI C:ssä ei ole säännöllisiä lausekkeita, mutta jo POSIX C:ssä olisi. Sama kävi mielessä, mutta näin matalan tason toteutuksessa on myös enemmän mietittävää: miten tiedostoa luetaan? rajoitetaanko merkkijonon kokoa, koska jo valmiiksi tiedetään rivin enimmäispituuden olevan 80 merkkiä, vai varaudutaanko rajattoman pitkiin riveihin?
Jep, jotain tällaista kuitenkin. en tosin testannut tuotakaan, joten virheitä voi olla. tulee ainakin mieleen tuo ö-kirjain joka ei taida mennä putc:llä...
#include <stdio.h>
int main(int argc, char *argv[]) {
FILE *fin, *fout;
int c;
if (!(fin = fopen("datafile.txt", "r")))
return -1;
if (!(fout = fopen("/dev/null", "w+")))
return -2;
while ((c = fgetc(fin)) != EOF) {
if (c == 'a') {
fputc('ö', fout);
fputc('ö', fout);
fputc('ö', fout);
fputc('n', fout);
}
else {
fputc(c, fout);
}
}
fclose(fin);
fclose(fout);
return 0;
}
-
Muokkasin koodia vähän, jotta näkisin, mitä se kirjoittaa ulos. Merkistökoodauksen kannalta lopputulos on puuroa (salmiakkikysymysmerkkejä), mutta oleellista on tietysti, että merkkejä kirjoitetaan ulos täsmälleen oikea määrä.
/* test.c */
#include <stdio.h>
int main(int argc, char *argv[]) {
int c;
while ((c = fgetc(stdin)) != EOF) {
if (c == 'a') {
fputc('ö', stdout);
fputc('ö', stdout);
fputc('ö', stdout);
fputc('n', stdout);
}
else
fputc(c, stdout);
}
return 0;
}
Lopputulos 40-megaisella tiedostolla:
tn@bahtin [~/Tilap]$ time ./test < aaa.dat > /dev/null
./test < aaa.dat > /dev/null 0,05s user 0,01s system 100% cpu 0,060 total
...joten tuskin 400-megainen kestäisi kovinkaan monta sekuntia :) Tosin: nyt ei siis käytetä säännöllisiä lausekkeita.
Muoks: Niin siis merkkejä kirjoitetaan ulos täsmälleen oikea määrä, mutta tavuja paljon vähemmän, koska jokaisesta ö-merkistä puuttuu nyt puolet UTF-8:n vaatimista tavuista.
-
Lopputulos 40-megaisella tiedostolla:
Koodia:
tn@bahtin [~/Tilap]$ time ./test < aaa.dat > /dev/null
./test < aaa.dat > /dev/null 0,05s user 0,01s system 100% cpu 0,060 total
40M, 0,060 s --> 667 MB/s ? Mikä levy ? SSD ?
-
40M, 0,060 s --> 667 MB/s ? Mikä levy ? SSD ?
Äh, enpä enää muista. Kone on Samsung X420 -läppäri, hdparm -I kertoo levystä seuraavaa:
/dev/sda:
ATA device, with non-removable media
Model Number: WDC WD3200BEVT-22ZCT0
Serial Number: WD-WXF0AA9X4369
Firmware Revision: 11.01A11
Transport: Serial, SATA 1.0a, SATA II Extensions, SATA Rev 2.5
Standards:
Supported: 8 7 6 5
Likely used: 8
Configuration:
Logical max current
cylinders 16383 16383
heads 16 16
sectors/track 63 63
--
CHS current addressable sectors: 16514064
LBA user addressable sectors: 268435455
LBA48 user addressable sectors: 625142448
Logical/Physical Sector size: 512 bytes
device size with M = 1024*1024: 305245 MBytes
device size with M = 1000*1000: 320072 MBytes (320 GB)
cache/buffer size = 8192 KBytes
Nominal Media Rotation Rate: 5400
Capabilities:
LBA, IORDY(can be disabled)
Queue depth: 32
Standby timer values: spec'd by Standard, with device specific minimum
R/W multiple sector transfer: Max = 16 Current = 0
Advanced power management level: 128
Recommended acoustic management value: 128, current value: 254
DMA: mdma0 mdma1 mdma2 udma0 udma1 udma2 udma3 udma4 udma5 *udma6
Cycle time: min=120ns recommended=120ns
PIO: pio0 pio1 pio2 pio3 pio4
Cycle time: no flow control=120ns IORDY flow control=120ns
Commands/features:
Enabled Supported:
* SMART feature set
Security Mode feature set
* Power Management feature set
* Write cache
* Look-ahead
* Host Protected Area feature set
* WRITE_BUFFER command
* READ_BUFFER command
* NOP cmd
* DOWNLOAD_MICROCODE
* Advanced Power Management feature set
SET_MAX security extension
Automatic Acoustic Management feature set
* 48-bit Address feature set
* Device Configuration Overlay feature set
* Mandatory FLUSH_CACHE
* FLUSH_CACHE_EXT
* SMART error logging
* SMART self-test
* General Purpose Logging feature set
* WRITE_{DMA|MULTIPLE}_FUA_EXT
* 64-bit World wide name
* IDLE_IMMEDIATE with UNLOAD
* Segmented DOWNLOAD_MICROCODE
* Gen1 signaling speed (1.5Gb/s)
* Gen2 signaling speed (3.0Gb/s)
* Native Command Queueing (NCQ)
* Host-initiated interface power management
* Phy event counters
* DMA Setup Auto-Activate optimization
Device-initiated interface power management
* Software settings preservation
* SMART Command Transport (SCT) feature set
* SCT Long Sector Access (AC1)
* SCT LBA Segment Access (AC2)
* SCT Error Recovery Control (AC3)
* SCT Features Control (AC4)
* SCT Data Tables (AC5)
unknown 206[12] (vendor specific)
unknown 206[13] (vendor specific)
Security:
Master password revision code = 65534
supported
not enabled
not locked
not frozen
not expired: security count
supported: enhanced erase
102min for SECURITY ERASE UNIT. 102min for ENHANCED SECURITY ERASE UNIT.
Logical Unit WWN Device Identifier: 50014ee258f70735
NAA : 5
IEEE OUI : 0014ee
Unique ID : 258f70735
Checksum: correct
-
Heräsi vain epäilys kun tuolta levyltä ei edes saa dataa ulos tuota vauhtia että mittausmenetelmässä on jotain vinossa. Kuitanneeko välimuisti sen tehdyksi ennen aikaisesti, tms.
Voisi kokeilla jollain isolla koetiedostolla laittaa "cat": lla tiedosto menemään harakoille >/dev/null että näkisi mitä vauhtia se levy antaa dataa ulos.
cat koe.txt >/dev/null
PS. Tuttu levy, sama kuin Acerin 5520 läppärissä alkuaan. Vaihdoin jossain vaiheessa tilalle muuten vastaavan mutta 500 G kokoisen kun tuo hajosi.
PPS. Laitoin kokeeksi Areenasta nauhoitetun Pikku Kakkosen videon (296 M) menemään tuonne pohjattomaan kaivoon ja se antoi datanopeudeksi vanhalla koneella ja vanhalla pienemmällä PATA-levyllä (Maxtor 6L200P0) noin 43 MB/s. Kävin Aceriakin kokeilemassa, näyttäsi antavan läppärissä noin 70 MB/s tuolla WD: n 500 G SATA-levyllä, malli WDC WD5000BPVT-0
-
Heräsi vain epäilys kun tuolta levyltä ei edes saa dataa ulos tuota vauhtia että mittausmenetelmässä on jotain vinossa.
Heh, pikku moka... Olin pätkäissyt tiedostoni pienemmäksi (kokeillakseni ruudulle kirjoittamalla, että tulos varmasti on odotustenmukaista) ja erehdyksessä tein varsinaisen testinkin sillä. Nyt palautin tiedoston 40-megaiseksi, ja tulos on jo vähän toinen:
tn@bahtin [~/Tilap]$ time ./test < aaa.dat > /dev/null
./test < aaa.dat > /dev/null 3,95s user 0,03s system 99% cpu 3,986 total
-
Hauska testi tosiaan.
Tässä on pari mielenkiintoista dilemmaa. Ihan ensiksi, koko ongelma (ja alkuperäinen kysymys) oli, että mikä "ohjelmointi"kieli on nopeampi ja ongelman määrittely oli metakielellä:
-luetaan tiedosto(a) levyltä
-tehdään hae-korvaa-toiminto määrätyillä kriteereillä
-tallenetaan lopputulos toisen tiedostoon
Tässä ongelman määrittelyssä ei siis pitäisi välittää siitä, onko jossain kielessä "säännöllisiä lausekkeita" vai ei. Tavoite oli tuottaa kyseisellä ohjelmointikielellä ja sen keinoilla _mahdollisimman nopea_ lopputulos. Jos ohjelmointikieli sallii nopeimman tavan, niin se siis sallittakoon.
Tuo C-kielinen pätkä lähentelee hyvää prototyyppiä "find-search"-toiminnosta, josta voidaan todeta, että olisi typerää lukea data ensin puskuriin (varata muistia) ja sitten alkaa korvaamaan sitä merkki kerrallaan, varsinkin kun muutettava merkki muutetaan pidemmäksi (tai lyhyemmäksi) merkkijonoksi (ja taas tarvittaessa varata muistia) jne...
C:n edut ja osoittimien käsittely tulee hyvin ilmi tässä kun ei tarvitse jokaisen osuman kohdalla kopioida loppubufferia eteenpäin korvattavien merkkien määrällä jne... Pukataan vain dataa raakana eteenpäin ja sillä siisti.
Tuossa protossa on kuitenkin (juuri) se (aavistelemani) vika, että se tukee vain "sizeof(char)" kokoa, joten unicode ei mene suoraan ja muut kuin ASCII mättää. Tuo voidaan tietty korvata fread ja fwrite -funktioilla, jolloin voidaan määrittää "character-encoding":n määräämä bittiluku.
Tunnustelemalla sen ja antamalla nuo syötteet parametrina, se on aika valmis find-replace-toiminto. Muutos tarvitaan vain kohtaan, misssä putc:t ovat jos osuma tulee. Siihen tarvitaan toinen looppi, joka katsoo korvattavan merkin pituuden.
Kokonaisuutena tämä oli erinomainen esimerkki siitä, että jokaisen ohjelmoijan kannatta(a/isi) miettiä miten tuollaisa aikaavieviä juttuja koodataan ja millä kielellä. Joskus on viisasta tehdä C:llä sopiva pikku funktio (="kirjasto", jonka tarvittavat funktiot on exportattu), jota sitten kutsutaan tarvittaessa toisista kielistä.
Sen päätöksen tekemiseen vaikuttavat businessprojekteissa ainakin seuraavat:
-käännetyn ohjelman suoritusaika (lopputuotteen nopeus)
-lähdekielen kirjoittamisen nopeus (RAD development)
-lähdekielen ylläpidettävyys (riskien hallinta varsinkin jos kieli on standardoimaton ja "tuntematon")
-lähdekielen laitteisto/alustariippumattomuus (toimii "kaikissa": Win, Linux, Mac, Unix...)
-käännetyn ohjelman sijoitus (voiko/onko oikeuksia laittaa se palvelimelle (esimerkiksi nettipalvelimelle))
Ja muistettakoon, että optimoida voi loputtomiin, mutta normitilanteessa (ainakin C) kääntäjä kyllä pääsääntöisesti optimoi nopeuden ja koodin koon paremmin kuin itse tehty ASM.
-
Täältä löytyy Java lähdekoodi + datafile mitä käytin: http://corei7.no-ip.biz/~petria/replaceTest/
Kannattanee pakata tuo datafile. :)
Pitänee itsekin kokeilla, pitää vaan jaksaa kirjoitella sopiva skripti tiedostoa varten.
Muokkaus: Pieni python satunnaisgeneraattori testailijoille tulostaa tavaran stdoutiin. Tekee 100 riviä (muuttujana, haluat ehkä vaihtaa vielä isommaksi) 1-80 merkkiä pitkiä rivejä a-kirjainta. Yksinkertaisuuden nimissä ohjelma ei ota mitään parametreja. Public domainia, ei tuossa kyllä mitään kovin kummosta koodia olekaan.
Pakkasin gzipillä, kun ei hyväksynyt py päätettä, purkaminen yksinkertaisesti: gunzip satunnaisdataa.py.gz
Suoritus (chmodauksen jälkeen): ./satunnaisdataa.py > data.txt
-
Tässä on pari mielenkiintoista dilemmaa. Ihan ensiksi, koko ongelma (ja alkuperäinen kysymys) oli, että mikä "ohjelmointi"kieli on nopeampi ja ongelman määrittely oli metakielellä:
-luetaan tiedosto(a) levyltä
-tehdään hae-korvaa-toiminto määrätyillä kriteereillä
-tallenetaan lopputulos toisen tiedostoon
Oikeastaan varsinainen kiistakapulamme oli ainoastaan keskimmäinen kohta eli itse datan käsittely, eivät I/O-operaatiot. On vain vaikea tehdä testiä skriptin ulkopuolelta (time-komennolla) niin, ettei vähintään ensimmäinen vaihe tule mukaan laskuun.
Tässä ongelman määrittelyssä ei siis pitäisi välittää siitä, onko jossain kielessä "säännöllisiä lausekkeita" vai ei.
Sinun määrittelyssäsi ei, mutta meidän kiistassamme kyse oli ensi sijassa siitä, mikä kieli on nopein, kun säännöllisin lausekkein täytyy isoja datatiedostoja muuntaa palvelimella web-käyttöliittymää varten.
Säännölliset lausekkeet ovat niin keskeinen osa Perlin perussyntaksia, että itsekin uskoin aluksi Perlin selviävän voittajaksi, joskin toivoin, ettei Python häviäisi kovin paljon. Tämä tulos ON minulle yllätys.
Muoks: Voittajasta ei vielä tiedä, mutta häviäjä löytyi :-\ Kokeilin D:tä, ja tulos oli masentava. 40-megainen tiedosto siirtyi Pythonilta /dev/nulliin nyt 17,29 sekunnissa, mutta käännetty D-ohjelma vei yli kymmenkertaisesti aikaa, 185,50 s. Jatkuvat tyypinvaihdot varmaankin kestävät, mutta D on vähän omituinen: joskus funktio ottaa argumentin char[], joskus argumentin string. Koodi tässä:
// test.d
import std.stdio;
import std.regex;
void main()
{
auto pattern = regex(r"a", "g");
string line_out;
foreach (line; stdin.byLine) {
line_out = cast(string) line;
foreach (m; match(line, pattern))
line_out = replace(line_out, pattern, "ööön");
writeln(line_out);
}
}
-
Pitipä virkistää C-taitoja ja sain kuin sainkin toimivan C-version aikaiseksi käyttäen hyväkseen glib,
se testin mukaan tosin on hitaampi kuin python-versio:
C
$ time ./a.out <datafile.txt >/dev/null
real 0m8.668s
user 0m8.613s
sys 0m0.036s
Koodi ja datafile.txt on täällä: http://corei7.no-ip.biz/~petria/replaceTest/
-
Pitipä virkistää C-taitoja ja sain kuin sainkin toimivan C-version aikaiseksi käyttäen hyväkseen glib,
se testin mukaan tosin on hitaampi kuin python-versio:
Hitauden syy on GRegex:n käyttö, joka on huono tähän toimenpiteeseen. Miksi ihmeessä teit sen noin?
-
Tässä ongelman määrittelyssä ei siis pitäisi välittää siitä, onko jossain kielessä "säännöllisiä lausekkeita" vai ei.
Sinun määrittelyssäsi ei, mutta meidän kiistassamme kyse oli ensi sijassa siitä, mikä kieli on nopein, kun säännöllisin lausekkein täytyy isoja datatiedostoja muuntaa palvelimella web-käyttöliittymää varten.
Lueppa uudelleen aloituksesi. Ketjun ensimmäisessä viestissä ei millään muotoa sanota, että ongelman määrittelyyn liittyisi säännölliset lausekkeet tai web-käyttöliittymä! Asia tuodaan lisäyksenä esiin vasta myöhemmissä kirjoituksissa.
Siispä minun määrittelyni on juuri sellainen millasesta ongelmasta alunperin oli kyse.
-
Pitipä virkistää C-taitoja ja sain kuin sainkin toimivan C-version aikaiseksi käyttäen hyväkseen glib,
se testin mukaan tosin on hitaampi kuin python-versio:
Hitauden syy on GRegex:n käyttö, joka on huono tähän toimenpiteeseen. Miksi ihmeessä teit sen noin?
Koska se nyt oli ainut järkevän helposti käytettävä toteutus C:llä regex:stä, joka siis alkuperäisessä tehtävässä oli tarkoitus olla käytössä.
-
Lueppa uudelleen aloituksesi. Ketjun ensimmäisessä viestissä ei millään muotoa sanota, että ongelman määrittelyyn liittyisi säännölliset lausekkeet tai web-käyttöliittymä! Asia tuodaan lisäyksenä esiin vasta myöhemmissä kirjoituksissa.
No anteeksi nyt kauheasti ::) Ovat ne silti oleellisia sen kiistan kannalta, mitä nyt yritetään ratkoa.
-
Kysyn tässä ketjussa kun kysymys ei ansaitse omaa ketjua: onko Pythonille, Perlille ja niiden kaltaisille tehty kehitysympäristöä(siis jonkunsortin IDE)?
-
Google ? Muokattu: Ei henkilökohtaisuuksia tai ilkeilyä - ajaaskel
Esim hakusanalla Perl IDE löytyy kaksi hyvää vaihtoehtoa, Padre ja eclipse terästettynä perl lisukkeilla, http://www.epic-ide.org/.
Sitten vain vaihtamaan Perl hakusananai mieleisesi ohjelmointikielen nimellä.
Joo ei löydy helposti.
-
Kaverikin jo myönsi itse kokeiltuaan Pythonin Perliä nopeammaksi, joten sikäli kisa on ratkennut. Minua vielä hotsittaisi kokeilla juttua jollain funktionaalisella kielellä (Scheme? Ocaml? Haskell? Erlang?).
-
Summarum vielä omat testit, käytin isompaa datatiedostoa sekä päivitin Java version lukemaan stdin +
lisäksi vielä ruby mukaan:
$ time ./test.py <datafile_large.txt >/dev/null
real 1m19.130s
user 1m18.569s
sys 0m0.396s
$ time ./test <datafile_large.txt >/dev/null
real 1m39.137s
user 1m38.066s
sys 0m0.840s
$ time java -cp build/ ReplaceTest <datafile_large.txt >/dev/null
real 1m45.373s
user 1m43.502s
sys 0m4.296s
$ time ./test.pe <datafile_large.txt >/dev/null
real 2m54.138s
user 2m52.763s
sys 0m0.964s
$ time ./test.rb <datafile_large.txt >/dev/null
real 4m2.874s
user 3m59.259s
sys 0m2.848s
Lähdekoodit + datafileet: http://corei7.serveirc.com/~petria/replaceTest/
-
Asia joka on syytä pitää mielessä: Pythonin säännölliset lausekkeet on ohjelmoitu C:llä. Jos tehtävänä on ohjelmoida jotakin säännöllisten lausekkeiden kaltaista, joka ei ole säännöllisiä lausekkeita, niin silloin Python ei ehkä ole mielekäs työkalu tehtävään.
-
Pascalia ei ollut vielä kokeiltukaan, joten vertailutulos Python vs. Free Pascal. 396-megainen koetiedosto.
tommin@wittgenstein [~/Tilap]$ time ./test.py < aaa.dat > /dev/null
./test.py < aaa.dat > /dev/null 176,85s user 1,66s system 99% cpu 2:58,90 total
tommin@wittgenstein [~/Tilap]$ time ./test < aaa.dat > /dev/null
./test < aaa.dat > /dev/null 106,21s user 186,55s system 99% cpu 4:54,28 total
Eli Pythonilta muutostyö vei 3 min, käännetyltä Pascal-ohjelmalta 5 min.
Kone oli nyt kotikonetta hitaampi työkone (Pentium 4 2,80 GHz, kiintolevy 80 Gt Maxtor), joten tulokset ovat vertailukelpoisia vain keskenään.
Pascal-ohjelma oli tällainen:
{ test.pas }
program test;
uses sysutils,
regexpr;
var line, newLine : AnsiString;
pattern : tregexprengine;
begin
if GenerateRegExprEngine('a', [], pattern) then begin
while not eof(input) do begin
readln(line);
RegExprReplaceAll(pattern, line, 'ööön', newLine);
writeln(newLine)
end
end
end.
-
Noiden regexpien käyttäminen ei välttämättä tuo ilmi koko totuutta. Python skriptin alussa on "import re", mikä siis tarkoittaa että ladataan lisäpalikka joka on itsessään kirjoitettu C:llä, eli tässä todennäköisesti verrataan Perlin ja Pythonin regexp toteutuksia keskenään, jotka molemmat on tod.näk. konepellin alla kirjoitettu C:llä.
Lisäksi noissa regexp toteutuksissa voi ehkä olla erilaisia optimointeja, joten tällainen "korvaa yksi merkki merkkijonolla" saattaa antaa ihan eri tuloksia kuin esim. "etsi oikeanmuotoinen sosiaaliturvatunnus kirjainmoskan seasta".
Tässä omat testiskriptini jotka ottavat numeron ja jakavat sen tekijöihin. Yritin tehdä koodit mahdollisimman identtisiksi molemmissa. Jos joku tietää optimointeja niin niitä saa ehdottaa.
Tässä mitataan siis raakaa laskentaa ja kielen perusominaisuuksia, jakolaskujen nopeutta, yksittäisten käskyjen suoritusnopeutta, silmukoiden nopeutta, jne.
Omalla koneellani Python3 versio vie n. 11 sekuntia ja Perl versio n. 5 sekuntia.
#!/usr/bin/env perl
my $n = 1032445450;
my @factors = ();
my $i = 1;
while($i < $n) {
if ($n % $i == 0) {
$n /= $i;
push(@factors, $i);
}
$i++;
}
push(@factors, $n);
@factors = sort { $a <=> $b } @factors;
# identtinen tulostus Python version kanssa
my $last = pop(@factors);
print "[";
foreach(@factors) { print "$_, "; }
print "$last]\n";
#!/usr/bin/env python3
n = 1032445450
factors=[]
i=1
while(i < n):
if (n % i == 0):
n //= i
factors.append(i)
i += 1
factors.append(n)
factors.sort()
print(factors)
-
Noiden regexpien käyttäminen ei välttämättä tuo ilmi koko totuutta. Python skriptin alussa on "import re", mikä siis tarkoittaa että ladataan lisäpalikka joka on itsessään kirjoitettu C:llä, eli tässä todennäköisesti verrataan Perlin ja Pythonin regexp toteutuksia keskenään, jotka molemmat on tod.näk. konepellin alla kirjoitettu C:llä.
Toki, mutta kiistamme ennakko-oletuksiin tämä testi sopi mainiosti. Tietysti vielä paremmin sopisi sellainen testi, jossa säännöllisen lausekkeen perusteella pitäisi löytää varioiva merkkijono, jota ei myöskään korvattaisi vakiojonolla vaan jolle pitäisi tehdä jotakin. (Esim. kääntää ”Meikäläinen, Matti” -> Matti Meikäläinen tai M. M.)
Raskas laskenta tehdään sitten tietysti mahdollisuuksien mukaan jotenkin muuten.
-
Tein vielä testin jossa ensin kirjoitin yhden julmettoman ison tiedoston jossa on sekaisin kirjaimia ja keksittyjä sosiaaliturvatunnuksia, eli sisältö tällaista: "...othlbdetqnnwimwfzs270995-257Xavckvgim...". Tässä ei siis ole erillisiä rivejä, vaan koko roska on yksi pitkä rivi.
Tässä koodinpätkä joka generoi tuon tiedoston, omasta tiedostostani tuli n. 50 megan kokoinen. Tämä siis laittaa kaiken stdouttiin, josta sen voi tallettaa tiedostoon:
#!/usr/bin/env python3
from random import randint, choice
from string import ascii_uppercase, ascii_lowercase
def sosnum():
return "%02d%02d%02d-%03d%s" % (
randint(1,30),
randint(1,12),
randint(0,99),
randint(1,600),
choice(ascii_uppercase+"0123456789"))
for i in range(1,100000):
for j in range(1,randint(100,1000)):
print(choice(ascii_lowercase), end="")
print(sosnum(), end="")
Sitten Perl ja Python skriptipätkät jotka lukevat stdinistä ja poimivat kaikki löytämänsä sos.turvatunnukset taulukkoon. Molemmat tulostavat lopuksi löytämiensä sos.turvatunnusten määrän ja kymmenen ensimmäistä taulukossa olevaa tunnusta. Omissa testeissäni Perl suoriutui urakasta n. 0,5 sekunnissa kun Pythonilta aikaa meni n. 3,5 sekuntia.
Tässä saattaa tosin vaikuttaa se että Python ei ilmeisesti voi tehdä tuota regexp operaatiota suoraan stdinille, vaan se joutuu ensin lukemaan sen kokonaan muistiin, kun taas Perl lähtee suoraan tekemään tuota hakua. Teen vielä yhden testin jossa tiedosto on pätkitty riveiksi, jos vaikka Python pärjäisi siinä tilanteessa paremmin.
Python:
#!/usr/bin/env python3
from sys import stdin
import re
m = re.compile('([0-9]{6}-[0-9]{3}[A-Z0-9])')
result = re.findall(m, stdin.read())
print(len(result))
print(result[0:10])
Perl:
#!/usr/bin/env perl
my @match = <STDIN> =~ m/([0-9]{6}-[0-9]{3}[A-Z0-9])/g;
print @match . "\n";
my @latest = @match[0..9];
print "['";
print join("', '", @latest);
print "']\n";
-
Tein nyt testin vielä siten että pilkoin tuon tiedoston riveiksi, mutta yllättäen lopputulos ei muuttunut juuri ollenkaan, Python on edelleen n. 7 kertaa hitaampi kuin Perl, eli 3,5s vs. 0,5s.
Muokkaus: Tajusin juuri että tein tuon regexpin hieman hölmösti tuossa Python koodissa, eli tein re.findall(m, line) kun olisin voinut tehdä m.findall(line). Korjasin tuon nyt tuohon koodiin, mutta Python koodi ei nopeutunut kuin ehkä 0,05s, ja tuokin on niin pieni ero että on vaikea sanoa onko se sattumaa vai oikeaa nopeutumista.
Muokkaus2: Huomasin juuri että jos tuosta regexpistä jättää nuo sulkeet pois niin Python versio nopeutuu jonkin verran, ero on enää 2s vs. 0,5s Perlin hyväksi. Tein muutoksen allaoleviin koodipätkiin.
Tässä ohjelma joka generoi tiedoston:
#!/usr/bin/env python3
from random import randint, choice
from string import ascii_uppercase, ascii_lowercase
def sosnum():
return "%02d%02d%02d-%03d%s" % (
randint(1,30),
randint(1,12),
randint(0,99),
randint(1,600),
choice(ascii_uppercase+"0123456789"))
for i in range(1,100000):
if (i % 10 == 0):
print()
for j in range(1,randint(100,1000)):
print(choice(ascii_lowercase), end="")
print(sosnum(), end="")
Tässä Python lukija:
#!/usr/bin/env python3
from sys import stdin
import re
m = re.compile('[0-9]{6}-[0-9]{3}[A-Z0-9]')
result = []
for line in stdin:
result += m.findall(line)
print(len(result))
print(result[0:10])
Ja tässä Perl lukija:
#!/usr/bin/env perl
my @found = ();
my @match;
while(<STDIN>) {
@match = $_ =~ m/[0-9]{6}-[0-9]{3}[A-Z0-9]/g;
push(@found, @match);
}
print @found . "\n";
my @first = @found[0..9];
print "['";
print join("', '", @first);
print "']\n";
-
Free Pascal näkyy elävän, joten täsmennetään:
uses sysutils,
regexpr; { pitää olla: oldregexpr ainakin Archissa }
Tein nyt testin vielä siten että pilkoin tuon tiedoston riveiksi, mutta yllättäen lopputulos ei muuttunut juuri ollenkaan, Python on edelleen n. 7 kertaa hitaampi kuin Perl, eli 3,5s vs. 0,5s.
50 megan tiedostolla tuo ero ei vielä ole mahdottoman suuri vaikka ”seitsemän kertaa hitaampi” onkin. Saatko kokeilluksi saman 500 megan tiedostolla, joka olisi lähempänä todellista testiä?
Sinänsä en epäile, en vain juuri nyt kerkeä itse kokeilemaan, kun pitää korjata tänäisiä yliopiston valintakokeita :)
-
50 megan tiedostolla tuo ero ei vielä ole mahdottoman suuri vaikka ”seitsemän kertaa hitaampi” onkin. Saatko kokeilluksi saman 500 megan tiedostolla, joka olisi lähempänä todellista testiä?
Lisäsin testitiedoston generoivan skriptin silmukkaan yhden nollan ja 535 megan tiedostolla ajat ovat Perl n. 5 sekuntia ja Python noin 20 sekuntia, eli melko täsmälleen ne samat kertoimet jotka saatiin jo 50 megan tiedostolla (kun oltiin poistettu regexpistä sulkeet, jotka jostain syystä oleellisesti hidastivat Pythonia).
Tuosta 50 megan tapauksestakin voi laskea että jos on vaikka 100kpl 50 megan tiedostoa niin siinä missä Perlillä menee kaikkien noiden läpikäymiseen 0,5s * 100 = 50s, niin Pythonilta menee samaan urakkaan 2,0s * 100 = 3min20s.
-
Lisäsin testitiedoston generoivan skriptin silmukkaan yhden nollan ja 535 megan tiedostolla ajat ovat Perl n. 5 sekuntia ja Python noin 20 sekuntia, eli melko täsmälleen ne samat kertoimet jotka saatiin jo 50 megan tiedostolla (kun oltiin poistettu regexpistä sulkeet, jotka jostain syystä oleellisesti hidastivat Pythonia).
OK. MItään dramaattista eroa ei sitten kuitenkaan ehdi vielä syntyä.
(Syy miksi ylipäänsä pyysin kokeilemaan isommalla tiedostolla on tietysti se, että nähtäisiin, kasvaako ero lineaarisesti vai eksponentiaalisesti vai ei ollenkaan. Mikä tahansahan on aina mahdollista ;))