LoginSignup
25
26

More than 3 years have passed since last update.

openpyxlで書き込み処理を行うときはlxmlと一緒に使うと高速化・省メモリー化できる

Last updated at Posted at 2021-02-21

はじめに

PythonでExcel形式のファイルを作るとき、openpyxlを使うことが多いと思います。
しかし、そのままのopenpyxlは遅い&メモリー効率が悪いという欠点があります。

解決方法

上述の欠点はデフォルトのXMLモジュールの性能が原因です。

実は、openpyxlはlxmlというより性能の良いXMLモジュールの使用にも対応しています。
https://foss.heptapod.net/openpyxl/openpyxl/-/blob/branch/3.0/openpyxl/cell/_writer.py#L72
lxmlはデフォルトのモジュールより高速かつ省メモリーに動作するため、lxmlの使用を有効化するとopenpyxlの書き込み処理の高速化&省メモリー化を期待できます。

lxmlの使用の有効化

openpyxlはlxmlモジュールを自動で読み込むため、
https://foss.heptapod.net/openpyxl/openpyxl/-/blob/branch/3.0/openpyxl/xml/__init__.py#L8)
pip install lxml してlxmlモジュールをインストールするだけで有効化されます

性能測定

測定コード(時間)

openpyxl_test.py
from line_profiler import LineProfiler
import memory_profiler
import openpyxl


def time():
    wb = openpyxl.Workbook(write_only=True)
    ws = wb.create_sheet()
    for _ in range(10000):
        ws.append(['%d' % i for i in range(100)])
    wb.save("test.xlsx")

prof = LineProfiler()
prof.add_function(time)
prof.runcall(time)
prof.print_stats()

結果(時間)

lxmlなし.txt
Timer unit: 1e-06 s

Total time: 61.3903 s
File: /xxx/openpyxl_test.py
Function: time at line 6

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     6                                           def time():
     7         1       1633.0   1633.0      0.0      wb = openpyxl.Workbook(write_only=True)
     8         1        724.0    724.0      0.0      ws = wb.create_sheet()
     9     10001       8888.0      0.9      0.0      for _ in range(10000):
    10     10000   20477116.0   2047.7     33.4          ws.append(['%d' % i for i in range(100)])
    11         1   40901916.0 40901916.0     66.6      wb.save("test.xlsx")
lxmlあり.txt
Timer unit: 1e-06 s

Total time: 27.064 s
File: /xxx/openpyxl_test.py
Function: time at line 6

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     6                                           def time():
     7         1       1612.0   1612.0      0.0      wb = openpyxl.Workbook(write_only=True)
     8         1        759.0    759.0      0.0      ws = wb.create_sheet()
     9     10001      10565.0      1.1      0.0      for _ in range(10000):
    10     10000   26444344.0   2644.4     97.7          ws.append(['%d' % i for i in range(100)])
    11         1     606738.0 606738.0      2.2      wb.save("test.xlsx")

wb.save("test".xml) が、lxmlなしの場合は40.9秒かかっているのに対して、lxmlありの場合は 0.6秒 です。

測定コード(メモリー)

openpyxl_test.py
from line_profiler import LineProfiler
import memory_profiler
import openpyxl

@memory_profiler.profile
def memory():
    wb = openpyxl.Workbook(write_only=True)
    ws = wb.create_sheet()
    for _ in range(10000):
        ws.append(['%d' % i for i in range(100)])
    wb.save("test.xlsx")

memory()

結果(メモリー)

lxmlなし.txt
Filename: /xxx/openpyxl_test.py

Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
     5     46.6 MiB     46.6 MiB           1   @memory_profiler.profile
     6                                         def memory():
     7     46.6 MiB      0.0 MiB           1       wb = openpyxl.Workbook(write_only=True)
     8     46.6 MiB      0.0 MiB           1       ws = wb.create_sheet()
     9    767.4 MiB      0.6 MiB       10001       for _ in range(10000):
    10    767.4 MiB    720.1 MiB     1030000           ws.append(['%d' % i for i in range(100)])
    11    102.6 MiB   -664.8 MiB           1       wb.save("test.xlsx")
lxmlあり.txt
Filename: /xxx/openpyxl_test.py

Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
     5     49.0 MiB     49.0 MiB           1   @memory_profiler.profile
     6                                         def memory():
     7     49.0 MiB      0.0 MiB           1       wb = openpyxl.Workbook(write_only=True)
     8     49.0 MiB      0.0 MiB           1       ws = wb.create_sheet()
     9     49.1 MiB      0.0 MiB       10001       for _ in range(10000):
    10     49.1 MiB      0.1 MiB     1030000           ws.append(['%d' % i for i in range(100)])
    11     49.7 MiB      0.5 MiB           1       wb.save("test.xlsx")

lxmlなしの場合は、ループ中に764.4-46.6=717.8MiB、 wb.save("test".xml) 時点でも102.6-46.6=56MiB使っているのに対し、
lxmlなしの場合は、 最大でも49.7-49.0=0.7MiB です。

ほか

StackOverflowのこの質問によるとlxmlなしでメモリーを17.2GB使っていた ものが、 lxmlありにすると40.1MBになった との報告もあります。

結論

openpyxlで書き込み処理する場合は何も考えずに pip install lxml しておこう。

25
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
26