RSS
 

Ruby, Python, Php e Lua in comparazione con files di grandi dimensioni

19 Apr

Vediamo come si comporta la sezione IO di alcuni tra i più popolari linguaggi script. L’esercizio consiste nel leggere sequenzialmente un grosso file di input e dividerlo in files più piccoli, in termini pratici, splittarlo.

I linguaggi in esame sono:
Ruby 1.8.6 p287 (2008-08-11) [i386-mswin32]
Ruby 1.8.7 p334 (2011-02-18) [i386-mingw32]
Ruby 1.9.2 p180 (2011-02-18) [i386-mingw32]
jruby 1.5.1 (ruby 1.8.7 patch 249) (Java HotSpot(TM) Client VM 1.6.0_14) [x86-java]
jruby 1.5.1 (ruby 1.8.7 patch 249) (Java HotSpot(TM) Client VM 1.6.0_24) [x86-java]
jruby 1.6.1 (ruby-1.8.7-p330) (Java HotSpot(TM) Client VM 1.6.0_24) [Windows XP-x86-java]
IronRuby 1.1.0.0 on .NET 4.0.30319.225
Python 2.6.2
Python 2.7.1
Python 3.2.0
Php 5.3.6 vc9 unsafe thread
Lua 5.1.4 40

Iniziamo col creare i tre files di input necessari per il test:
ruby new.rb input1.txt 185000 1799 => 330Mb
ruby new.rb input2.txt 500000 1799 => 880Mb
ruby new.rb input3.txt 1500000 1799 => 2,6Gb

Il test l’ho eseguito su un PC con cpu Intel E7300 Core2 Duo 2,66Ghz Ram 3,25Gb con Windows XP Professional 32bit, Hard Disk ST3250310AS Barracuda 7200.10 SATA 3.0Gb/s da 250Gb.

Prossimamente lo eseguirò anche su un Server Windows 2008 R2 64bit su VMWare Xeon X7460 Dual Core a 2,66Ghz e 2Gb di ram con dischi SCSI.

Prima e dopo aver creato i tre files di input ho deframmentato il disco. Se i tempi sono incostanti significa che il disco deve essere deframmentato o c’è qualcosa che rallenta il sistema come ad esempio l’antivirus che deve essere disabilitato.

Per ogni file ho eseguito sei elaborazioni e considerando le scarse prestazioni IO del sistema, ho scartato le tre peggiori. Naturalmente, prima di ogni test ho eliminato i files di output.

I grafici parlano da soli.
L’unico commento che posso fare riguarda la versione 1.9.2 di ruby che ha evidenti problemi di IO e questi risultati non sono in linea con le performance generali di questo linguaggio che, come ho potuto rilevare da altri precedenti test, sono invece molto buone.

Questi sono gli script che ho scritto per l’occasione:

# Written by Marco Mastrodonato on 19/04/2011
# Script to split a file into n output files
# Example:
# ruby split.rb par1 par2
# par1 => name [default => input1.txt]
# par2 => record number that determines the number of output files [default => 1650]
 
strinput = ARGV[0] || 'input1.txt'
nrec_to_split = ARGV[1] ? ARGV[1].to_i : 1650
 
unless File.exists? strinput
	puts "File #{strinput} doesn't exists!" 
	exit 1
end
 
stroutput = "out_%03d.txt"
 
t1= Time.now
puts "Ruby #{RUBY_VERSION} #{strinput} started at #{t1}, wait please..."
 
File.open(strinput, "r") do |f|
	nsplit = 0
	nrec = 0
	fileoutput = nil
 
	while line = f.gets
		if nrec % nrec_to_split == 0
			nsplit += 1
			fileoutput.close if fileoutput
			fileoutput = File.open(stroutput % nsplit, 'w')
		end
		fileoutput.write line
		nrec += 1
	end
 
	fileoutput.close if fileoutput
end
 
puts "Ended at #{Time.now}"
puts "Elapsed time #{Time.now - t1}"
exit 0
# Written by Marco Mastrodonato on 19/04/2011
# Script to split a file into n output files
# Example:
# python split.py par1 par2
# par1 => name [default => input1.txt]
# par2 => record number that determines the number of output files [default => 1650]
 
from time import time, gmtime, strftime
import sys
 
try:
	strinput = sys.argv[1]
except:
	strinput = 'input1.txt'
 
stroutput = "out_%03d.txt"
 
try:
	nrec_to_split = int(sys.argv[2])
except:
	nrec_to_split = 1650
 
t1 = time()
print(sys.version)
print(strftime("Started at %a, %d %b %Y %H:%M:%S +0000, wait please...", gmtime()))
 
nrec = 0
nsplit = 0
 
fileinput = open(strinput, "r")
for line in fileinput:
	if nrec % nrec_to_split == 0:
		try:
			fileoutput.close()
		except NameError:
			fileoutput = None
		nsplit += 1
		fileoutput = open(stroutput %nsplit , "w")
	fileoutput.write(line)
	nrec += 1    
fileoutput.close()
fileinput.close()
 
print(strftime("Ended at %a, %d %b %Y %H:%M:%S +0000", gmtime()))
print("Elapsed time %f" %(time() - t1))
<?php
// Written by Marco Mastrodonato on 19/04/2011
// Script to split a file into n output files
// Example:
// php split.php par1 par2
// par1 => name [default => input1.txt]
// par2 => record number that determines the number of output files [default => 1650]
 
$strinput = isset($argv[1]) ? $argv[1] : 'input1.txt';
$nrec_to_split = isset($argv[2]) ? $argv[2] : 1650;
$stroutput = 'out_%03d.txt';
 
$t1 = microtime_float();
echo "Php ".phpversion()." started at ".date('D, d M Y H:i:s T').", wait please...\n";
 
$nsplit = 0;
$nrec = 0;
$fileinput=fopen($strinput,"r");
 
while(!feof($fileinput)) {
	if ($nrec % $nrec_to_split == 0) {
		++$nsplit;
		if (isset($fileoutput)) fclose($fileoutput);
		$fileoutput = fopen(sprintf($stroutput, $nsplit), 'w');
	}
	$buffer = fgets($fileinput);
	fwrite($fileoutput, $buffer);
	++$nrec;
}
 
fclose ($fileinput);
 
echo "Ended at ".date('D, d M Y H:i:s T')."\n"; 
echo "Elapsed time ".(microtime_float() - $t1)."\n";
 
 
function microtime_float() {
	list($usec, $sec) = explode(" ", microtime());
	return ((float)$usec + (float)$sec);
}
 
?>
--[[
Written by Marco Mastrodonato on 19/04/2011
Script to split a file into n output files
Example:
lua split.lua par1 par2
par1 => name [default => input1.txt]
par2 => record number that determines the number of output files [default => 1650]
--]]
strinput = arg and arg[1] or "input1.txt"
stroutput = "out_%03d.txt"
nrec_to_split = arg and arg[2] and tonumber(arg[2]) or 1650
 
local t1 = os.clock()
print(_VERSION .. " started at " .. os.date("%a, %d %b %Y %H:%M:%S +0000"))
 
nsplit = 0
nrec = 0
for line in io.lines(strinput) do
  if nrec % nrec_to_split == 0 then
    if fileOut ~= nil then io.close(fileOut) end
    nsplit = nsplit + 1
    fileOut = io.open(string.format(stroutput, nsplit) , 'w')
  end
  fileOut:write (line .. '\n')
  nrec = nrec + 1
end
 
io.close(fileOut)
 
print("Ended at " .. os.date("%a, %d %b %Y %H:%M:%S +0000"))
print(string.format("Elapsed time: %.2f\n", os.clock() - t1))

Per creare i files ho usato questo semplice script ruby:

# Example:
# ruby new.rb [NOME] [LINES] [RECORD SIZE]
 
stroutput = ARGV[0] || 'input1.txt'
num = ARGV[1] ? ARGV[1].to_i : 185000
size = ARGV[2] ? ARGV[2].to_i : 1799
 
if File.exists? stroutput
	puts "File #{stroutput} already exists!" 
	exit 1
end
 
t1= Time.now
puts "Ruby #{RUBY_VERSION} #{stroutput} started at #{t1}, wait please..."
 
line = "*" * size
 
File.open(stroutput, "w") do |f|
	num.times do
		f.puts line
	end
end
 
puts "Ended at #{Time.now}"
puts "Elapsed time #{Time.now - t1}"
exit 0
 

Tags: , , , , , ,

  1. Michele

    19 April 2011 at 22:32

    Ho provato ruby 1.92 e con python2.6.6.
    Slackware 64 bit. Python è quello pacchettizzato, mentre ruby l’ho compilato dai sorgenti.
    Il pc è un portatile, il processore è P8400, 4G di ram

    file 1 330 Mega
    ruby 4.15, python 3.99
    file 2 900 Mega (cosi me l’ha creato il tuo script)
    ruby 29.13, python 29.72
    file 3 2700 Mega
    ruby 91.06, python 91.39

    il dubbio che la versione di ruby 1.9.2 che usi abbia un problema.