質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
多次元配列

1次元配列内にさらに配列を格納している配列を、多次元配列と呼びます。

NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

2214閲覧

numpy配列多重ループのリスト内包表記による高速化失敗

shmpwk

総合スコア13

多次元配列

1次元配列内にさらに配列を格納している配列を、多次元配列と呼びます。

NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

1クリップ

投稿2020/01/11 01:27

概要

numpy配列の多重ループの処理が遅く,リスト内包表記で書くと高速化すると聞いたので試してみましたが,むしろ低速化してしまい,その原因を探っています.

環境

Model Name: MacBook Pro Model Identifier: MacBookPro14,1 Processor Name: Intel Core i5 Processor Speed: 2.3 GHz Number of Processors: 1 Total Number of Cores: 2

jupyter lab 0.35.4
python 3.7.5

コード

import numpy as np import time inn1 = np.zeros((10,10000,100,100))+1 inn2 = np.zeros((10,10000,100,100))+1 inn3 = np.zeros((10,10000,100,100))+1 start1 = time.time() inn3 = np.array([[[[inn2[k][h][i][j] + inn1[k][h][i][j] * inn1[k][h][i][j] for j in range(100)] for i in range(100)] for h in range(10000)] for k in range(10)]) end1 = time.time() dif1 = end1 - start1 print('dif1',dif1) inn1 = np.zeros((10,10000,100,100))+1 inn2 = np.zeros((10,10000,100,100))+1 start2 = time.time() for k in range(10): for h in range (10000): for i in range(100): for j in range(100): inn2[k][h][i][j] += inn1[k][h][i][j] * inn1[k][h][i][j] end2 = time.time() dif2 = end2 - start2 print('dif2',dif2)

output

dif1 28585.88311481476 dif2 2316.2408690452576

考察

リスト内包表記で速くなるのは,numpy.appendのループの例をよく見かけるのですが,上記のような単なる演算だと,あまり速度が変わらないのかなと思いました.もちろん,pythonをcythonで書き換えたり,multiprocessingやJoblibで高速化しますが,それ以外の方法で高速化できないかなと思っています.

退会済みユーザー👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/01/11 01:59

ループ回数を減らしたところ、ある回数より下では内包表記の方が早くなりました。不思議な感じですので+1しました。 参考) ※インデントはforのところだけ適当にお願いします。 import numpy as np import time # 検証しやすいように小さく変更 k_ = 50 h_ = 80 i_ = 10 j_ = 20 inn1 = np.zeros((k_,h_,i_,j_))+1# np.zeros((10,10000,100,100))+1 inn2 = np.zeros((k_,h_,i_,j_))+1# np.zeros((10,10000,100,100))+1 ##inn3 = np.zeros((2,5,7,9))+1# np.zeros((10,10000,100,100))+1 start1 = time.time() inn3 = np.array([[[[inn2[k][h][i][j] + inn1[k][h][i][j] * inn1[k][h][i][j] for j in range(j_)] for i in range(i_)] for h in range(h_)] for k in range(k_)]) end1 = time.time() dif1 = end1 - start1 print('dif1',dif1) inn1 = np.zeros((k_,h_,i_,j_))+1# np.zeros((10,10000,100,100))+1 inn2 = np.zeros((k_,h_,i_,j_))+1# np.zeros((10,10000,100,100))+1 start2 = time.time() for k in range(k_): for h in range (h_): for i in range(i_): for j in range(j_): inn2[k][h][i][j] += inn1[k][h][i][j] * inn1[k][h][i][j] end2 = time.time() dif2 = end2 - start2 print('dif2',dif2) # 答え合わせ print((inn3==inn2).all())
shmpwk

2020/01/11 05:20

こちらのコードをループ回数をそのまま実行したところ, dif1 1.8996009826660156 dif2 1.784618854522705 となり,リスト内包表記の方が遅くなりました...
退会済みユーザー

退会済みユーザー

2020/01/11 18:06

これがk,h,i,jの回数を減らすと内包表記の方が早くなり、値を上げていくと何が原因か不明ですが内包表記の方が遅くなる(forの方が早くなる)現象が起きました。このPCの場合、概算4-6秒くらいかかる処理で逆転現象が起きています。
guest

回答1

0

ベストアンサー

その原因を探っています

私も詳しくはありませんが気になったので調べていたところ、探る方法としてPythonのdisモジュールに行き当たりました。

解析結果は以下の通りです。
アセンブル結果

■内包表記
4回内包するために、4回リストを作って、4回タプルを作って、4回追加して…に加え、LOAD_CLOSUREとLOAD_DEREFにかなりの回数を割いています。

■for文
forを4回するためには、上記の処理がゼロになる他は一部の処理が2倍に増えるのと数回だけ処理が増えるくらいで済んでいます。

ですので、一命令にかかる時間はそれぞれ違うとは思いますが、内包表記にするためのコスト(主にLOAD_CLOSUREとLOAD_DEREF、およびリスト関連の処理)の方がfor文にするコストの方が高いため遅い、と思われます。

イメージ説明
※グラフの最大の時間は私の手元のPCではなく、掲載の値です。for文については最大の時間も手元で計測しましたが、おおむね差がなかったため掲載の値を流用しました。

Python3

1import numpy as np 2import time 3import dis 4 5k_ = 10 6h_ = 10 7i_ = 10 8j_ = 10 9 10inn1 = np.zeros((k_,h_,i_,j_))+1 11inn2 = np.zeros((k_,h_,i_,j_))+1 12 13def comprehensive(inn1,inn2): 14 15 # start = time.time() 16 inn3 = np.array([[[[inn2[k][h][i][j] + inn1[k][h][i][j] * inn1[k][h][i][j] for j in range(j_)] for i in range(i_)] for h in range(h_)] for k in range(k_)]) 17 # end = time.time() 18 # dif = end - start 19 # print('CMP',dif) 20 return inn3 21 22def for_loop(inn1,inn2): 23 24 # start = time.time() 25 for k in range(k_): 26 for h in range (h_): 27 for i in range(i_): 28 for j in range(j_): 29 inn2[k][h][i][j] += inn1[k][h][i][j] * inn1[k][h][i][j] 30 # end = time.time() 31 # dif = end - start 32 # print('FOR',dif) 33 return inn2 34 35print("---------------------------") 36dis.dis(comprehensive) 37print("---------------------------") 38dis.dis(for_loop) 39 40ans_comp = comprehensive(inn1,inn2) 41ans_for = for_loop(inn1,inn2) 42 43# 答え合わせ 44print((ans_comp==ans_for).all())

アセンブル関係のタグやC関係のタグをつけるとより詳し説明ができる人にも見てもらえると思います。

投稿2020/01/12 04:18

編集2020/01/12 04:21
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shmpwk

2020/01/12 04:57

非常に丁寧なご説明をありがとうございます. disモジュール知りませんでした.内包表記は思わぬところでコストがかかってしまっているのですね. ちなみにですが,グラフの出典(掲載値)は何でしょうか?
退会済みユーザー

退会済みユーザー

2020/01/12 07:55

グラフのデータは全て掲載のコードを手元のPCで走らせたときの計算時間(print()で得られた数値)と計算回数(for文の一番内側の実行回数=k_*h_*i_*j_)の値です。表のデータはdisで得られた命令をExcelでカウントしたものです。試してはいませんが、もしかしたら、一番内側だけ内包で他はforにするような使い方が最速かもしれませんね。
shmpwk

2020/01/12 11:28

僕も手元で色々計算時間を出してみましたが,総じてfor文だけの方が速そうです. 色々情報ありがとうございます.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問