Apache Arrowの凄さを体感する

データ分析とかをしていると大規模データを扱うことがある。
複数のライブラリを使う際にデータ連携を行う際に一度CSVJSONに出力して連携先ではそれをパースしてといった方法をとることがある。
数メガくらいのファイルであれば問題にはならないが、これがギガなどになってくるとこのデータ連携コストが無視できなくなってくる。

これを解決する方法の1つとしてApache Arrowというものがある。
今回はこれを紹介して実際にどれくらい早いのかを検証してみる。

Apache Arrowとは?

  • 2016年の10月に0.1.0がリリース
  • メモリ上でカラム型データを扱うためのフォーマットとアルゴリズム

カラム型でデータを格納するので効率よく圧縮することが出来、メモリ上に書き込むことで読み書きの速さを実現している。
昔はメモリなどのリソースは潤沢に使うことは用意ではなかったが、昨今ではAWSなどで何十Gものメモリを積んだマシンを使用することが出来るため、このようなものが生きてくる。
このApache ArrowはPython, RubyやC, C++などから扱うことが現状出来る。

実際に速さを体験してみる

検証について

今回はEC2のt2.largeを使って検証を行った

Ubuntu: 16.04.2  
CPU: 2
メモリ: 8G   

Python: 3.6.2  
Ruby: 2.4.1  
Arrow: 0.6.0

今回はこれを使ってPythonCSV、Arrowで2.3Gのデータを出力しRubyでそれぞれを読み込んだ時の速度を見る。

CSV

Pythonでの書き込み

import pandas as pd
import pyarrow as pa
from time import time

df = pd.DataFrame({"a": ["a" * i for i in list(range(40000))],
                   "b": ["b" * i for i in list(range(40000))],
                   "c": ["c" * i for i in list(range(40000))]})

start_time = time()
df.to_csv("test.csv", header=False)
print("write : {0}s".format(time() - start_time))

Rubyでの読み込み

require "time"
require "csv"

start_time = Time.now
CSV.open("test.csv", headers: false) do |row|
   puts "batch size : #{row.count}"
end
puts "read : #{Time.now - start_time}s"

Apache Arrow

Pythonでの書き込み

#-*- using:utf-8 -*-
import pandas as pd
import pyarrow as pa
from time import time

df = pd.DataFrame({"a": ["a" * i for i in list(range(40000))],
                   "b": ["b" * i for i in list(range(40000))],
                   "c": ["c" * i for i in list(range(40000))]})
start_time = time()
record_batch = pa.RecordBatch.from_pandas(df)
with pa.OSFile("/dev/shm/pandas.arrow", "wb") as sink:
    schema = record_batch.schema
    writer = pa.RecordBatchFileWriter(sink, schema)
    writer.write_batch(record_batch)
    writer.close()
print("write : {0}s".format(time() - start_time))

Rubyでの読み込み

require "arrow"
require "time"
require "csv"

Input = Arrow::MemoryMappedInputStream

start_time = Time.now
Input.open("/dev/shm/pandas.arrow") do |input|
  reader = Arrow::RecordBatchFileReader.new(input)
  puts "batch size : #{reader.get_record_batch(0).count}"
end
puts "read : #{Time.now - start_time}s"

検証

CSVの時

Write (s) Read (s)
1 51.62104869 4.85522798
2 51.43777633 4.864032677
3 51.35622239 4.885216672
4 51.46343231 4.865155221
5 51.39711213 4.870614692

Apache Arrowの時

Write (s) Read (s)
1 3.39462328 0.013111846
2 3.312984943 0.013321837
3 3.332154036 0.013229807
4 3.342362165 0.013370328
5 3.306367636 0.012740001

それぞれの場合で5回ほど実行して平均をとると
CSVの時が書き込みに51.46秒、読み込みに4.87秒
Apache Arrowの時が書き込みに3.34秒、読み込みに0.01秒

書き込みも読み込みも早くなってますね!!