openpyxlでエクセル書き込みが遅い原因を調べたら、セル書式だった(Lambda)

openpyxlでエクセル書き込みが遅い原因を調べたら、セル書式だった(Lambda)

セル書式の利用は計画的に。
Clock Icon2025.03.27

私たち製造ビジネステクノロジー部では、製造業に関するエンドユーザや事業会社さんに対して、システムやサービスを事業会社さんと一緒に作成・運用しています。そんな製造業において、PLCなどのデータをエクセルにまとめるため、プログラムでエクセルを作る機会がありました。
openpyxlでエクセルを作成してみると、かなりの時間が必要でした。いろいろ調べてみたところ、セル書式の有無で時間が変わったので、本記事にまとめました。

おすすめの方

  • AWS Lambdaでopenpyxlを利用したい方
  • AWS Lambdaでopenpyxlでセル書式を利用したい方
  • AWS Lambdaでopenpyxlでセル書式を利用した場合の処理時間を知りたい方

Lambdaをデプロイする

sam init

sam init \
    --runtime python3.11 \
    --name lambda-openpyxl-sample \
    --app-template hello-world \
    --no-tracing \
    --no-application-insights \
    --structured-logging \
    --package-type Zip

requirements.txtにライブラリを記載する

requirements.txt
openpyxl

SAMテンプレート

メモリは2048MBで固定とします。

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: lambda-openpyxl-sample

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: lambda-openpyxl-sample-function
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.11
      MemorySize: 2048
      Timeout: 900
      Architectures:
      - x86_64

  HelloWorldFunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}

Lambdaコード

書き込むセルに対して、次の設定をしています。

  • フォント
    • 太字
    • 大きさ16
  • 罫線
    • 上下左右あり
  • 数値フォーマット
    • 0.0(小数1桁)

それぞれ、次の時間を計測しました。

  • エクセルにデータを追加する時間
  • エクセル自体を保存する時間
app.py
import openpyxl
import time

from openpyxl.cell import Cell
from openpyxl.styles import Font, Border, Side

MY_CELL_BORDER = Border(
    left=Side(style="thin"),
    right=Side(style="thin"),
    top=Side(style="thin"),
    bottom=Side(style="thin"),
)

MY_CELL_FONT = Font(bold=True, size=16)

def lambda_handler(event, context):
    wb = openpyxl.Workbook()
    ws = wb.active

    # 30万行 x 10列のデータを書き込む
    begin_write = time.perf_counter()
    for i in range(300_000):
        # rowとcolで小数を作ってセルに書き込む
        ws.append([make_cell(ws, float(f"{i}.{j}")) for j in range(0, 10)])
    end_write = time.perf_counter()
    print(f"Write time: {(end_write - begin_write)} s")

    begin_save = time.perf_counter()
    wb.save("/tmp/hello_world.xlsx")
    end_save = time.perf_counter()
    print(f"Save time: {end_save - begin_save} s")

def make_cell(ws, value):
    # セルごとに書式を設定する
    cell = Cell(ws, value=value)
    cell.font = MY_CELL_FONT
    cell.border = MY_CELL_BORDER
    cell.number_format = "0.0"
    return cell

if __name__ == "__main__":
    lambda_handler(None, None)

セル書式なしの場合は、次のようにコメントアウトして試しました。

def make_cell(ws, value):
    # セルごとに書式を設定する
    cell = Cell(ws, value=value)
    # cell.font = MY_CELL_FONT
    # cell.border = MY_CELL_BORDER
    # cell.number_format = "0.0"
    return cell

デプロイ

sam build --use-container
sam deploy

Lambdaを実行してみる

aws lambda invoke \
    --function-name lambda-openpyxl-sample-function \
    output.txt

実行結果

セル書式の有無で明らかに実行時間が変化しました。

セル書式 行追加[s] 保存[s] 全体[s] メモリ使用量[MB]
あり 76.6 44.1 120.7 1470
なし 10.4 36.5 46.9 1102

write-only modeで試してみる

WriteOnlyCellを利用します。(書式を設定しないのに WriteOnlyCell を利用するのは意味がないと思いますが、比較実験のため利用しています。)

WriteOnlyCellを利用しない場合は、下記を参考にしてください。

Lambdaコード

app.py
import openpyxl
import time

from openpyxl.cell import WriteOnlyCell
from openpyxl.styles import Font, Border, Side

MY_CELL_BORDER = Border(
    left=Side(style="thin"),
    right=Side(style="thin"),
    top=Side(style="thin"),
    bottom=Side(style="thin"),
)

MY_CELL_FONT = Font(bold=True, size=16)

def lambda_handler(event, context):
    wb = openpyxl.Workbook(write_only=True)
    ws = wb.create_sheet()

    # 30万行 x 10列のデータを書き込む
    begin_write = time.perf_counter()
    for i in range(300_000):
        # rowとcolで小数を作ってセルに書き込む
        ws.append([make_cell(ws, float(f"{i}.{j}")) for j in range(0, 10)])
    end_write = time.perf_counter()
    print(f"Write time: {(end_write - begin_write)} s")

    begin_save = time.perf_counter()
    wb.save("/tmp/hello_world.xlsx")
    end_save = time.perf_counter()
    print(f"Save time: {end_save - begin_save} s")

def make_cell(ws, value):
    # セルごとに書式を設定する
    cell = WriteOnlyCell(ws, value=value)
    cell.font = MY_CELL_FONT
    cell.border = MY_CELL_BORDER
    cell.number_format = "0.0"
    return cell

セル書式なしの場合は、先ほどと同じくコメントアウトして試します。

実行結果

write-only modeでも、セル書式の有無で実行時間が変わりました。

セル書式 行追加[s] 保存[s] 全体[s] メモリ使用量[MB]
あり 134.0 3.1 137.1 201
なし 50.8 3.2 54.0 183

まとめ

今回のテストコードでは、write-only modeだと合計の実行時間が増えました。WriteOnlyCellを利用する影響かもしれません。(メモリ使用量を少なくするための影響など。)

write-only セル書式 行追加[s] 保存[s] 全体[s] メモリ使用量[MB]
No あり 76.6 44.1 120.7 1470
No なし 10.4 36.5 46.9 1102
Yes あり 134.0 3.1 137.1 201
Yes なし 50.8 3.2 54.0 183

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.