時事ネタは突っ込み所が多すぎて全然関係のない話題が続いていますが、今回もそんなネタ。
最近のスクリプトや言語は型や配列に関してものすごい自由度が高いのですが、その代償として何かしらの犠牲を伴っています。
私が言語に最初にどっぷりと漬かった言語はturbo-pascalなのですが、その原因はどうしても細かい所まで見て実装を確認しないと処理が上手く回らなかったり、処理時間を短くするために内部でどのように動いているのかまで見ないといけなかったからだと思います。
あと付属するマニュアルが結構読み応え満点で実際にそうなっているかどうかは別にして、結構突っ込んだ事まで解説されてたりしました。日本語版なので訳者の思い入れが加味されていたのかもしれませんが。
基本的な考え方として、配列は単なるメモリ空間なので、最初に領域を決めたら拡張することができません。動的に変動させるためにはどうするか?というところで実装の仕方は色々な考え方でうまく乗り越えているだけのこと。
追加に対して代償を減らすには領域確保で指定された領域以上に予め確保することで時間的リソースの節約を行います。これはどんな形の配列でも変わりません。繰り返すことでメモリー空間に分断された状態になると、どうしてもアクセス効率が落ちるので、最終的にはガーベージコレクトや再生成するなどして綺麗な状態に保つ必要があるかもしれません。とはいっても、すでにCPUキャッシュやらなんちゃらキャッシュやら、広大なメモリー空間のおかげで実害は出てこないのも事実。ただおまかせ状態だとそれなりに何らかのリソースの浪費はしていると思います。
一番非効率なやり方は、新たに配列全体の領域+追加分の領域を確保して、配列の内容のコピーを行って不要になった領域を解放するという方法ではないでしょうか?これの代償としては配列に対してメモリアドレスなどで直接参照しているものがある場合、参照している部分も変更する必要があるので、結構な処理負荷が出てきたり、放置すると簡単に処理が落ちることになります。
これを結構積極的に行っている言語というかシェルというか物が、おそらくAndroidだと思います。基本的な考え方として、すべてのオブジェクトインスタンスは最悪破棄して再生成すれば元通りという発想のもと、リソース不足になると積極的に破棄したり、どこかに追いやられてしまいます。最近のバージョンではかなりうまい仕組みが導入されて改善されていますが、その辺がどうしても好きになれない場合は苦痛しかないかも。
とはいえ、非効率なやり方の利点として、追加しても常に新規に領域確保された状態になるので配列へのアクセスでまずおかしなことは発生しません。配列の要素へのアクセス速度も常に変わらない状態でアクセスできるため、タイミングの影響を受けるようなこともまず発生しません。
で、なんでこんなたかが配列への要素の追加というくだらない話をだらだら書いているのかといえば、pythonスクリプトで結構安易に .append()で要素の追加を行っている部分を見て、最近気にしたことはなかったけどこの辺どうなのかな?とちょっと突っ込んでみたところ、やはり結構処理的に割高な処理なんだと再認識したのでグダりましたw
たしかに便利なんですけどねw処理時間がμ秒単位の場合はやめた方が良いかも。どうしてもという場合はpythonの場合はarray.array('b')とか、要素の追加を減らす処理に変更するとか、そもそも要素の追加をさせない形にするとか、仕様を見直した方がいいんじゃないかな?
実際にどんな処理でこの辺の処理時間が気になっかと言えば、DHTxxのライブラリっぽいやつで、エラーが発生したときにどんな状態で発生しているのか気になったので色々と書き換えて気になりました。
どのくらいのインパクトがあるかといえば…raspberry pi 3b+でループでGPIOピンの状態をサンプリングしている処理があるのですが、そこでループのタイミングで配列に要素を追加してサンプリングしていたときは、最大25回ぐらいだったのですが、これを信号が変動した時だけにした場合、最大30回ぐらいになりました。「ただし」が付くのですが、raspberry piで使用しているCPUはマルチコアとなっていてメインとなるプロセッサー0とその他とで処理能力の差があるようで、25回→10回、30回→10回ぐらいに簡単に落ち込みました。処理が落ちてる状態なら変わりないということではなく、おそらく不利になるのがGPIOなどのIO周りのウェイトがプロセッサーによって異なっているためだと思います。逆に言えば、現状ではメモリアクセスの時間リソースへの影響が小さくなったという結果なのかもしれませんw(結局何が言いたいんだ(;´д`)
0 件のコメント:
コメントを投稿