Улучшите проверку производительности numpy при загрузке с ssd

#python #performance #numpy #pickle

Вопрос:

Bacground: Я храню коллекцию массивов numpy на твердотельном накопителе (около 500 тыс. файлов, достаточно больших, чтобы вместить все в память), и мне нужно загружать образцы пакетами. Поэтому, чтобы выяснить, какой формат является наиболее эффективным, я провел этот тест:

 import numpy as np
import pickle
from datetime import datetime
import random


def numpy_read_speed_test():

    root_folder = "C:\temp\"

    def pickle_read(pypic):
        with open(pypic, "rb") as pypif:
            return pickle.load(pypif)

    num_files = 1000

    file_names = [(root_folder   f"np_comp_{n}.npz",
                   root_folder   f"np_pickle_{n}.npy",
                   root_folder   f"np_flat_{n}.npy",
                   root_folder   f"picle_{n}.pkl") for n in range(num_files)]

    print(f"generating {num_files} test files")
    for np_comp, np_pickle, np_flat, py_pickle in file_names:

        # generate realistic dummy data
        arr = np.random.normal(0.0, 10.0, (144*16, 37))
        msk = np.random.randint(-1, 2, (1, 37))
        arr = (arr*msk).astype('float32')

        np.save(np_flat, arr, allow_pickle=False)
        np.save(np_pickle, arr, allow_pickle=True)
        np.savez_compressed(np_comp, x=arr)

        with open(py_pickle, "wb") as f:
            pickle.dump(arr, f)

    for _ in range(5):

        sample_size = num_files//2

        print(f"      testing numpy flat format: ", end='', flush=True)
        t0 = datetime.utcnow()
        npflt = [np.load(np_flat) for _, _, np_flat, _ in random.sample(file_names, sample_size)]
        t1 = datetime.utcnow()
        print(f"{t1 - t0} - {(t1 - t0).total_seconds()/sample_size} seconds per file", flush=True)

        print(f"testing numpy compressed format: ", end='', flush=True)
        t0 = datetime.utcnow()
        npcpz = [np.load(np_comp)['x'] for np_comp, _, _, _ in random.sample(file_names, sample_size)]
        t1 = datetime.utcnow()
        print(f"{t1 - t0} - {(t1 - t0).total_seconds()/sample_size} seconds per file", flush=True)

        print(f"   testing python pickle format: ", end='', flush=True)
        t0 = datetime.utcnow()
        pypkl = [pickle_read(py_pickle) for _, _, _, py_pickle in random.sample(file_names, sample_size)]
        t1 = datetime.utcnow()
        print(f"{t1 - t0} - {(t1 - t0).total_seconds()/sample_size} seconds per file", flush=True)

        print(f"testing numpy picle-able format: ", end='', flush=True)
        t0 = datetime.utcnow()
        npplk = [np.load(np_pickle) for _, np_pickle, _, _ in random.sample(file_names, sample_size)]
        t1 = datetime.utcnow()
        print(f"{t1 - t0} - {(t1 - t0).total_seconds()/sample_size} seconds per file", flush=True)

        print(f"lengths: {len(npplk)}, {len(npflt)}, {len(npcpz)}, {len(pypkl)}", flush=True)
        print(f"shapes: {npplk[0].shape}, {npflt[0].shape}, {npcpz[0].shape}, {pypkl[0].shape}", flush=True)
        print("", flush=True)


if __name__ == '__main__':
    numpy_read_speed_test()

 

Проблема в том, что тест дает противоречивые результаты:

 generating 1000 test files
      testing numpy flat format: 0:00:02.893651 - 0.005787302 seconds per file
testing numpy compressed format: 0:00:03.028680 - 0.00605736 seconds per file
   testing python pickle format: 0:00:01.840414 - 0.003680828 seconds per file
testing numpy picle-able format: 0:00:02.779625 - 0.00555925 seconds per file
lengths: 500, 500, 500, 500
shapes: (2304, 37), (2304, 37), (2304, 37), (2304, 37)

      testing numpy flat format: 0:00:01.682378 - 0.003364756 seconds per file
testing numpy compressed format: 0:00:02.062464 - 0.004124928 seconds per file
   testing python pickle format: 0:00:00.985221 - 0.001970442 seconds per file
testing numpy picle-able format: 0:00:01.518342 - 0.003036684 seconds per file
lengths: 500, 500, 500, 500
shapes: (2304, 37), (2304, 37), (2304, 37), (2304, 37)

      testing numpy flat format: 0:00:00.891200 - 0.0017824 seconds per file
testing numpy compressed format: 0:00:01.429321 - 0.002858642 seconds per file
   testing python pickle format: 0:00:00.515116 - 0.001030232 seconds per file
testing numpy picle-able format: 0:00:00.888200 - 0.0017764 seconds per file
lengths: 500, 500, 500, 500
shapes: (2304, 37), (2304, 37), (2304, 37), (2304, 37)

      testing numpy flat format: 0:00:00.448100 - 0.0008962 seconds per file
testing numpy compressed format: 0:00:01.226276 - 0.0024525519999999998 seconds per file
   testing python pickle format: 0:00:00.351079 - 0.0007021579999999999 seconds per file
testing numpy picle-able format: 0:00:00.699157 - 0.0013983140000000001 seconds per file
lengths: 500, 500, 500, 500
shapes: (2304, 37), (2304, 37), (2304, 37), (2304, 37)

      testing numpy flat format: 0:00:00.460103 - 0.000920206 seconds per file
testing numpy compressed format: 0:00:01.140257 - 0.002280514 seconds per file
   testing python pickle format: 0:00:00.292065 - 0.0005841300000000001 seconds per file
testing numpy picle-able format: 0:00:00.491111 - 0.000982222 seconds per file
lengths: 500, 500, 500, 500
shapes: (2304, 37), (2304, 37), (2304, 37), (2304, 37)
 

Существует большая разница в первом раунде и в следующем. Я полагаю, что это связано с системным кэшированием данных. Как бы вы улучшили тест?

Правка: Изначально была ошибка, из-за которой казалось, что вариация была очень высокой, но все равно похоже, что тест можно улучшить.

Комментарии:

1. Ну, цифры составляют десять тысячных доли секунды, и отклонения имеют аналогичный масштаб. Это вряд ли можно назвать непоследовательностью, скорее это естественное отклонение.