Python はコンパイラ側から見て非常によくできた言語だと思う。不必要に機能を入れ込んでいない点が何より素晴らしい。コンパイラを作るという視点から見ると普通の機能も別の意味があることに気が付く。
Python だけではないが、foreach のような構文を書ける言語はいくつもある。記述に便利なだけでなく、書き方によってはシリアライズが確定するのでコンパイラのオプティマイゼーションが効きやすくなるだろう。
どういうことか?簡単なリストの for 文を考える。
a_lst = [1, 2, 3, 4, 5]
for e in a_lst:
print(e)
a_lst は必ず最初から順番にアクセスされる。一方、C で各 for 文では次のようになるだろう。
int a[] = {1, 2, 3, 4, 5};
for (int i = 0 ;i < sizeof(a)/sizeof(int); i++ ) {
printf("%d\n", a[i]);
}
配列 a は何かに index される(この場合は変数 i)。配列 a がランダムアクセスされないことは、for の中を解析しないとわからない。配列をポインタに変えても本質は変わらない。
表記だけでそのアクセス方法がわかることはコンパイラにとって都合がよい。とりわけ HLS では最適化の可能性が広がる。
unroll の実際
前置きが長くなったが for 文の unroll を使った例を示そう。unroll は for 文の中身をマクロのように展開する。
from polyphony import testbench
from polyphony import unroll, rule
def unroll01(xs, ys):
s = 0
with rule(unroll='full'):
for i in range(8):
x = xs[i] + 1
if x < 0:
s = s + x
else:
s = s - x
ys[i] = x
#print(x)
return s
@testbench
def test():
data = [1, 2, 3, 4, 5, 6, 7, 8]
out_data = [0] * 8
s = unroll01(data, out_data)
print(s)
assert -44 == s
assert 2 == out_data[0]
assert 3 == out_data[1]
assert 4 == out_data[2]
assert 5 == out_data[3]
assert 6 == out_data[4]
assert 7 == out_data[5]
assert 8 == out_data[6]
assert 9 == out_data[7]
test()
実際に unroll をした場合と、しない場合でのシミュレーション上の結果は 38 clock と 52 clock となった。
unroll の回数が決定できない場合は数字を指定することができる。回数は丁度良い必要はない。この例では 8 のループに対し 3 ずつの展開をする。あまりの 2 は逐次的に実行される。
with rule(unroll='3'):
for i in range(8):
x = xs[i] + 1
...後略...
将来的には書き方が変わるかもしれない。最新のバージョンでは次の書き方もサポートされている。with 文を省きインデントの混乱が少なく書けるように設計されている。
def unroll04_a(xs:list):
sum = 0
for x in unroll(xs, 4):
sum += x
return sum