Тесты: у python есть более быстрый способ перехода в сетевую папку?
Мне нужно пройти через папку с примерно десяти тысячами файлов. Мой старый vbscript очень медленно справляется с этим. Поскольку я начал использовать Ruby и Python с тех пор, я сделал тест между тремя языками сценариев, чтобы увидеть, какой из них лучше всего подходит для этой работы.
Результаты тестов ниже в подмножестве из 4500 файлов в общей сети:
Python: 106 seconds
Ruby: 5 seconds
Vbscript: 124 seconds
То, что Vbscript было бы самым медленным, не было неожиданностью, но я не могу объяснить разницу между Ruby и Python. Является ли мой тест для Python не оптимальным? Есть ли более быстрый способ сделать это в Python?
Тест для thumbs.db предназначен только для теста, в действительности есть больше тестов.
Мне нужно что-то, что проверяет каждый файл на пути и не производит слишком большого объема вывода, чтобы не мешать времени. Результаты немного отличаются друг от друга, но не намного.
#python2.7.0
import os
def recurse(path):
for (path, dirs, files) in os.walk(path):
for file in files:
if file.lower() == "thumbs.db":
print (path+'/'+file)
if __name__ == '__main__':
import timeit
path = '//server/share/folder/'
print(timeit.timeit('recurse("'+path+'")', setup="from __main__ import recurse", number=1))
'vbscript5.7
set oFso = CreateObject("Scripting.FileSystemObject")
const path = "\\server\share\folder"
start = Timer
myLCfilename="thumbs.db"
sub recurse(folder)
for each file in folder.Files
if lCase(file.name) = myLCfilename then
wscript.echo file
end if
next
for each subfolder in folder.SubFolders
call Recurse(subfolder)
next
end Sub
set folder = oFso.getFolder(path)
recurse(folder)
wscript.echo Timer-start
#ruby1.9.3
require 'benchmark'
def recursive(path, bench)
bench.report(path) do
Dir["#{path}/**/**"].each{|file| puts file if File.basename(file).downcase == "thumbs.db"}
end
end
path = '//server/share/folder/'
Benchmark.bm {|bench| recursive(path, bench)}
EDIT: поскольку я подозревал, что печать вызвала задержку, я протестировал скрипты с печатью всех 4500 файлов, а также печать без изменений, разница остается: R: 5 P: 107 в первом случае и R: 4.5 P: 107 в последний
EDIT2: на основе ответов и комментариев здесь представлена версия Python, которая в некоторых случаях может работать быстрее, пропуская папки
import os
def recurse(path):
for (path, dirs, files) in os.walk(path):
for file in files:
if file.lower() == "thumbs.db":
print (path+'/'+file)
def recurse2(path):
for (path, dirs, files) in os.walk(path):
for dir in dirs:
if dir in ('comics'):
dirs.remove(dir)
for file in files:
if file.lower() == "thumbs.db":
print (path+'/'+file)
if __name__ == '__main__':
import timeit
path = 'f:/'
print(timeit.timeit('recurse("'+path+'")', setup="from __main__ import recurse", number=1))
#6.20102692
print(timeit.timeit('recurse2("'+path+'")', setup="from __main__ import recurse2", number=1))
#2.73848228
#ruby 5.7
Ответы
Ответ 1
Реализация Ruby для Dir
находится в C (файл dir.c
, в соответствии с этой документацией). Однако эквивалент Python реализован в Python.
Не удивительно, что Python менее эффективен, чем C, но подход, используемый в Python, дает немного большую гибкость - например, вы можете пропустить целые поддеревья, например, например. '.svn'
, '.git'
, '.hg'
при перемещении иерархии каталогов.
В большинстве случаев реализация Python выполняется достаточно быстро.
Обновление: Пропуск файлов/поддисков вообще не влияет на скорость обхода, но общее время, затрачиваемое на обработку дерева каталогов, безусловно, может быть уменьшено, потому что вам не нужно трассировать потенциально большие поддеревья главного дерева. Сэкономленное время, конечно, пропорционально тому, сколько вы пропустите. В вашем случае, который выглядит как папки изображений, маловероятно, что вы сэкономите много времени (если только изображения не находились под контролем ревизии, когда пропущенные поддеревья, принадлежащие системе контроля версий, могут иметь какое-то влияние).
Дополнительное обновление: Пропуск папок выполняется путем изменения значения dirs
:
for root, dirs, files in os.walk(path):
for skip in ('.hg', '.git', '.svn', '.bzr'):
if skip in dirs:
dirs.remove(skip)
# Now process other stuff at this level, i.e.
# in directory "root". The skipped folders
# won't be recursed into.
Ответ 2
Я настраиваю структуру каталогов следующим образом:
for i in $(seq 1 4500); do
if [[ $i -lt 100 ]]; then
dir="$(for j in $(seq 1 $i); do echo -n $i/;done)"
mkdir -p "$dir"
touch ${dir}$i
else
touch $i
fi
done
Это создает 99 файлов с путями длиной 1-299 и 4401 файлами в корне структуры каталогов.
Я использовал следующий ruby script:
#!/usr/bin/env ruby
require 'benchmark'
def recursive(path, bench)
bench.report(path) do
Dir["#{path}/**/**"]
end
end
path = 'files'
Benchmark.bm {|bench| recursive(path, bench)}
Я получил следующий результат:
user system total real
files/ 0.030000 0.090000 0.120000 ( 0.108562)
Я использую следующий python script с помощью os.walk:
#!/usr/bin/env python
import os
import timeit
def path_recurse(path):
for (path, dirs, files) in os.walk(path):
for folder in dirs:
yield '{}/{}'.format(path, folder)
for filename in files:
yield '{}/{}'.format(path, filename)
if __name__ == '__main__':
path = 'files'
print(timeit.timeit('[i for i in path_recurse("'+path+'")]', setup="from __main__ import path_recurse", number=1))
Я получил следующий результат:
0.250478029251
Итак, похоже, что Ruby все еще работает лучше. Было бы интересно посмотреть, как это выполняется на вашем наборе файлов в общем сетевом ресурсе.
Было бы также интересно увидеть, что этот script работает на python3 и с jython и, возможно, даже с pypy.