9


1

インターリーブされたデータを高速に読み取る方法は?

複数のチャネルのデータを含むファイルを持っています。 ファイルはベースレートでサンプリングされ、各チャネルはそのベースレートをある数で割った値でサンプリングされます。これは常に2のべき乗のようですが、それは重要ではないと思います。

したがって、1、2、および4のディバイダーでサンプリングされたチャネル_a b_、および_c_がある場合、ストリームは次のようになります。

a0 b0 c0 a1 a2 b1 a3 a4 b2 c1 a5 ...

追加の楽しみのために、チャネルは独立してfloatまたはintにすることができ(それぞれについて知っていますが)、データストリームは必ずしも2の累乗で終了するわけではありません。サンプルストリームはさらに拡張することなく有効です。 値は時々大きく、時にはリトルエンディアンですが、私は何を前向きに扱っているか知っています。

これらを適切にアンパックし、numpy配列に正しい値を入力するコードがありますが、遅いです:それは次のように見えます(私はあまり多くのことを考えすぎないことを望みます;単にアルゴリズムのアイデアを与える):

for sample_num in range(total_samples):
    channels_to_sample = [ch for ch in all_channels if ch.samples_for(sample_num)]
    format_str = ... # build format string from channels_to_sample
    data = struct.unpack( my_file.read( ... ) ) # read and unpack the data
    # iterate over data tuple and put values in channels_to_sample
    for val, ch in zip(data, channels_to_sample):
        ch.data[sample_num / ch.divider] = val

そして、遅いです-私のラップトップで20MBのファイルを読むのに数秒。 プロファイラーは、私が `Channel#samples_for()`で多くの時間を費やしていると言っています-これは理にかなっています。条件付きロジックが少しあります。

私の脳は、ループをネストするのではなく、一気にこれを行う方法があるように感じています-おそらくインデックスを使用して各配列に必要なバイトを読み込むのですか? 1つの巨大で非常識なフォーマット文字列を構築するという考えは、疑問を投げかける道のようにも思えます。

更新

回答してくれた人に感謝します。 numpyのインデックス作成のコツにより、テストデータの読み取りに必要な時間が約10秒から約0.2秒に短縮され、速度は50倍になりました。

3 Answer


6


実際にパフォーマンスを改善する最良の方法は、すべてのサンプルでPythonループを取り除き、NumPyにコンパイル済みCコードでこのループを実行させることです。 これを達成するのは少し難しいですが、可能です。

まず、少し準備が必要です。 Justin Peelが指摘したように、サンプルが配置されるパターンは、いくつかのステップの後に繰り返されます。 d_1、…​、d_kがk個のデータストリームの除数であり、b_1、…​、b_kがバイト単位のストリームのサンプルサイズであり、lcmがこれらの除数の最小公倍数である場合、

N = lcm*sum(b_1/d_1+...+b_k/d_k)

ストリームのパターンが繰り返されるバイト数になります。 最初のNバイトがそれぞれどのストリームに属しているかがわかったら、このパターンを繰り返すことができます。

次のようにして、最初のNバイトのストリームインデックスの配列を作成できます。

stream_index = []
for sample_num in range(lcm):
    stream_index += [i for i, ch in enumerate(all_channels)
                     if ch.samples_for(sample_num)]
repeat_count = [b[i] for i in stream_index]
stream_index = numpy.array(stream_index).repeat(repeat_count)

ここで、「d」はシーケンスd_1、…​、d_k、「b」はシーケンスb_1、 …​, b_k.

今あなたはできる

data = numpy.fromfile(my_file, dtype=numpy.uint8).reshape(-1, N)
streams = [data[:,stream_index == i].ravel() for i in range(k)]

`reshape()`を機能させるために、データの最後に少しパディングする必要があるかもしれません。

これで、各ストリームに属するすべてのバイトが別々のNumPy配列になりました。 各ストリームの `dtype`属性に割り当てるだけで、データを再解釈できます。 最初のストリームをビッグエンディアンの整数として解釈する場合は、次のように記述します。

streams[0].dtype = ">i"

これにより、配列のデータが変更されることはなく、解釈される方法が変更されます。

これは少し不可解に見えるかもしれませんが、パフォーマンスに関してははるかに優れているはずです。


2


`channel.samples_for(sample_num)`を `iter_channels(channels_config)`イテレータに置き換えます。イテレータは内部状態を保持し、1回のパスでファイルを読み取ることができます。 このように使用してください。

for (chan, sample_data) in izip(iter_channels(), data):
    decoded_data = chan.decode(sample_data)

イテレータを実装するには、周期が1のベースクロックを考えます。 さまざまなチャネルの周期は整数です。 チャンネルを順番に繰り返し、周期を法とするクロックがゼロの場合にチャンネルを出力します。

for i in itertools.count():
    for chan in channels:
        if i % chan.period == 0:
            yield chan


1


http://docs.python.org/library/itertools.html [`grouper()`レシピ]と `itertools.izip()`がここで役立つはずです。