Java開発メモ 2010/10/14

Javaのツールを作っているときに悩んで相談したログです。 今回のお題は何かの処理をカウントしつつ 過去1.000秒以内に実行した回数を返すクラス。 相談に乗ってくれた方ありがとうございました!
0
SH2 @sh2nd

Counterクラスというのを作って、複数スレッドからadd()してカウンタをインクリメントする。getCount(double sec)で過去sec秒にadd()された回数を返したい。add()、getCount()とも毎秒5万回は呼びたいけど効率のよい方法が思いつかない

2010-10-14 13:07:12
SH2 @sh2nd

あーdouble secはコンストラクタで指定してそのインスタンスでは固定、でよい

2010-10-14 13:14:04
SH2 @sh2nd

LinkedBlockingQueue案:各スレッドがput()しつつメンテナンススレッドがsec経過したものをtake()しまくる、カウンタはsize()でとる → take()間に合わないだろう常考

2010-10-14 13:18:22
さくらずみずみFM @royzumi

@sh2nd 時刻はSystem.currentTimeMillis()で取得して、切り下げて秒にする。で、ConcurrentMapのKeyに時刻、valueにAtomicIntegerをputしてカウントするのかな。

2010-10-14 13:31:00
さくらずみずみFM @royzumi

@sh2nd だけど、毎秒5万回だとMapのget処理に時間がかかりそうなので、毎回ConcurrentMapにアクセスしないように工夫したほうがいいのかもしれない。でもsynchronizedかかるなら一緒か?やってみないとわからないなぁ

2010-10-14 13:32:16
さくらずみずみFM @royzumi

うん、ThreadLocalに忍ばせるという手もあるな

2010-10-14 13:41:33
SH2 @sh2nd

DB的にはINSERT、DELETE WHERE t < NOW() - 1、COUNT(*) WHERE t >= NOW() - 1、というオペレーションだと考えれば、DELETEとCOUNT(*)をO(1)でやるのはあきらめざるを得ないのかなー

2010-10-14 13:44:42
SH2 @sh2nd

@royzumi ミリ秒必須なのです><

2010-10-14 13:45:04
さくらずみずみFM @royzumi

@sh2nd getCount(double sec)のsecがミリ秒と言うことですか?

2010-10-14 13:47:02
SH2 @sh2nd

. @royzumi getCount()の引数は整数でよいけど、プログラム開始5.456秒にgetCount(1)したら4.456~5.456秒の間にincされた回数を返してほしい

2010-10-14 13:50:49
SH2 @sh2nd

1ミリ秒に配列をちぎって管理すれば、add()とexpireされたデータの削除は簡単だけど、getCount()で1,000要素にアクセスしないといけない

2010-10-14 14:01:48
さくらずみずみFM @royzumi

@sh2nd 性能が満足できないと思う。あとListで、ある時刻以前のデータを削除するのはやり難いと思う。

2010-10-14 14:05:35
さくらずみずみFM @royzumi

@sh2nd 配列を使う方法もある。Countする最大の時間。たとえば5000ミリ秒だと5000個分のAtomicIntegerの配列を作るとか。カウンターリセットタイミングが難しいけど。

2010-10-14 14:06:17
SH2 @sh2nd

@royzumi ミリ秒単位の配列を用意しつつ、SUMを取る処理をチューニングできれば、って感じがしてきました

2010-10-14 14:12:11
SH2 @sh2nd

とりあえず1ミリ秒単位の配列を作って、add()、getCount()はsynchronizedしてしまう版を作ってみるか

2010-10-14 14:25:46
SH2 @sh2nd

配列を循環構造にしてリサイクルするのが存外めんどくさい

2010-10-14 16:21:30
たむたむ🏫 @tamtam180

@sh2nd 各配列で保持するのであれば、累計で保持すれば差分を計算するだけなのでO(1)、もしくはO(2)になります。

2010-10-14 21:14:36
たむたむ🏫 @tamtam180

javaならAtomicIntegerArrayをサイクルバッファとして使って累計保持にすればさくっとできる予感。

2010-10-14 21:16:23
SH2 @sh2nd

@tamtam180 あー、1ミリ秒ごとにtotal += count[i] - count[i - 1000]すればいいですね。ありがとうございます!

2010-10-14 21:22:16
たむたむ🏫 @tamtam180

@sh2nd ただ前回のインデックス番号を明示的に保持しておかないと、歯抜けになった時に死亡します(´・ω・`)

2010-10-14 21:27:02
SH2 @sh2nd

@tamtam180 100ミリ秒おいてイベントが発生したときにサイクルバッファの該当100要素をどのスレッドが0クリアするのか、とかがめんどくさいですね(´・ω・`)

2010-10-14 21:30:33
SH2 @sh2nd

System.nanoTime()ベンチマークになっていた

2010-10-25 10:56:16
SH2 @sh2nd

マルチスレッドでSystem.nanoTime()をすると、時間が巻き戻ることがあるってことなのかもしかして。CPUコアによって時計が違う

2010-10-25 11:17:51
SH2 @sh2nd

うーんこれか。nanoTimeはかならず単調増加すると思って書いたプログラムがマルチスレッドにしたとたんバグった http://www.02.246.ne.jp/~torutk/javahow2/time.html#doc1_id95

2010-10-25 11:19:17
SH2 @sh2nd

とったnanoTimeに対して if (nanoT < prevMax) nanoT = prevMax; すればいいかな

2010-10-25 11:21:49