質問編集履歴
6
誤字など修正
test
CHANGED
File without changes
|
test
CHANGED
@@ -136,7 +136,7 @@
|
|
136
136
|
|
137
137
|
* キャッシュなし
|
138
138
|
|
139
|
-
* LRUしないキャッシュ
|
139
|
+
* LRUで削除しないキャッシュ
|
140
140
|
|
141
141
|
* Collections.syncronizedMap() で排他するLRUキャッシュ
|
142
142
|
|
@@ -172,11 +172,11 @@
|
|
172
172
|
|
173
173
|
class NonCache<K, V> implements Calculator<K, V>{
|
174
174
|
|
175
|
-
|
175
|
+
private Function<K, V> initializer;
|
176
|
-
|
176
|
+
|
177
|
-
|
177
|
+
NonCache(Function<K, V> initializer){ this.initializer = initializer; }
|
178
|
-
|
178
|
+
|
179
|
-
@Override public V get(K key){ return initializer.apply(key); }
|
179
|
+
@Override public V get(K key){ return initializer.apply(key); }
|
180
180
|
|
181
181
|
}
|
182
182
|
|
@@ -186,106 +186,106 @@
|
|
186
186
|
|
187
187
|
class NonLruCache<K, V> implements Calculator<K, V>{
|
188
188
|
|
189
|
-
|
189
|
+
private int capacity;
|
190
|
-
|
190
|
+
|
191
|
-
|
191
|
+
private Function<K, V> initializer;
|
192
|
-
|
192
|
+
|
193
|
-
|
193
|
+
private Map<K, V> map = new LinkedHashMap<>(capacity, 1.0f, true);
|
194
|
-
|
194
|
+
|
195
|
-
|
195
|
+
NonLruCache(int capacity, Function<K, V> initializer){
|
196
|
-
|
196
|
+
|
197
|
-
|
197
|
+
this.capacity = capacity;
|
198
|
-
|
198
|
+
|
199
|
-
|
199
|
+
this.initializer = initializer;
|
200
|
+
|
201
|
+
}
|
202
|
+
|
203
|
+
@Override public V get(K key){ return map.computeIfAbsent(key, initializer); }
|
204
|
+
|
205
|
+
}
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
// Collections.syncronizedMap() で排他するLRUキャッシュ
|
210
|
+
|
211
|
+
class LruCache1<K, V> implements Calculator<K, V>{
|
212
|
+
|
213
|
+
private int capacity;
|
214
|
+
|
215
|
+
private Function<K, V> initializer;
|
216
|
+
|
217
|
+
private Map<K, V> map = Collections.synchronizedMap(new LinkedHashMap<K, V>(capacity, 1.0f, true){
|
218
|
+
|
219
|
+
@Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest){ return size() > capacity; }
|
220
|
+
|
221
|
+
});
|
222
|
+
|
223
|
+
LruCache1(int capacity, Function<K, V> initializer){
|
224
|
+
|
225
|
+
this.capacity = capacity;
|
226
|
+
|
227
|
+
this.initializer = initializer;
|
228
|
+
|
229
|
+
}
|
230
|
+
|
231
|
+
@Override public V get(K key){ return map.computeIfAbsent(key, initializer); }
|
232
|
+
|
233
|
+
}
|
234
|
+
|
235
|
+
|
236
|
+
|
237
|
+
// StampedLock.writeLock() で排他するLRUキャッシュ
|
238
|
+
|
239
|
+
class LruCache2<K, V> implements Calculator<K, V>{
|
240
|
+
|
241
|
+
private int capacity;
|
242
|
+
|
243
|
+
private StampedLock lock = new StampedLock();
|
244
|
+
|
245
|
+
private Function<K, V> initializer;
|
246
|
+
|
247
|
+
private Map<K, V> map = new LinkedHashMap<K, V>(capacity, 1.0f, true){
|
248
|
+
|
249
|
+
@Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest){ return size() > capacity; }
|
250
|
+
|
251
|
+
};
|
252
|
+
|
253
|
+
LruCache2(int capacity, Function<K, V> initializer){
|
254
|
+
|
255
|
+
this.capacity = capacity;
|
256
|
+
|
257
|
+
this.initializer = initializer;
|
258
|
+
|
259
|
+
}
|
260
|
+
|
261
|
+
@Override public V get(K key){
|
262
|
+
|
263
|
+
long stamp = lock.writeLock();
|
264
|
+
|
265
|
+
try{ return map.computeIfAbsent(key, initializer); } finally{ lock.unlockWrite(stamp); }
|
266
|
+
|
267
|
+
}
|
268
|
+
|
269
|
+
|
270
|
+
|
271
|
+
}
|
272
|
+
|
273
|
+
|
274
|
+
|
275
|
+
// daisuke7さんに教えていただいたbarakbさんのライブラリ https://github.com/barakb/Cache
|
276
|
+
|
277
|
+
class LruCache3<K, V> implements Calculator<K, V>{
|
278
|
+
|
279
|
+
private Cache<K, V> cache;
|
280
|
+
|
281
|
+
LruCache3(int capacity, Function<K, V> initializer){ cache = new Cache<>(initializer::apply, capacity); }
|
282
|
+
|
283
|
+
@Override public V get(K key){
|
284
|
+
|
285
|
+
try{ return cache.get(key); } catch(Throwable throwable){ throw new RuntimeException(throwable); }
|
200
286
|
|
201
287
|
}
|
202
288
|
|
203
|
-
@Override public V get(K key){ return map.computeIfAbsent(key, initializer); }
|
204
|
-
|
205
|
-
}
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
// Collections.syncronizedMap() で排他するLRUキャッシュ
|
210
|
-
|
211
|
-
class LruCache1<K, V> implements Calculator<K, V>{
|
212
|
-
|
213
|
-
private int capacity;
|
214
|
-
|
215
|
-
private Function<K, V> initializer;
|
216
|
-
|
217
|
-
private Map<K, V> map = Collections.synchronizedMap(new LinkedHashMap<K, V>(capacity, 1.0f, true){
|
218
|
-
|
219
|
-
@Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest){ return size() > capacity; }
|
220
|
-
|
221
|
-
});
|
222
|
-
|
223
|
-
LruCache1(int capacity, Function<K, V> initializer){
|
224
|
-
|
225
|
-
this.capacity = capacity;
|
226
|
-
|
227
|
-
this.initializer = initializer;
|
228
|
-
|
229
|
-
}
|
230
|
-
|
231
|
-
@Override public V get(K key){ return map.computeIfAbsent(key, initializer); }
|
232
|
-
|
233
|
-
}
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
// StampedLock.writeLock() で排他するLRUキャッシュ
|
238
|
-
|
239
|
-
class LruCache2<K, V> implements Calculator<K, V>{
|
240
|
-
|
241
|
-
private int capacity;
|
242
|
-
|
243
|
-
private StampedLock lock = new StampedLock();
|
244
|
-
|
245
|
-
private Function<K, V> initializer;
|
246
|
-
|
247
|
-
private Map<K, V> map = new LinkedHashMap<K, V>(capacity, 1.0f, true){
|
248
|
-
|
249
|
-
@Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest){ return size() > capacity; }
|
250
|
-
|
251
|
-
};
|
252
|
-
|
253
|
-
LruCache2(int capacity, Function<K, V> initializer){
|
254
|
-
|
255
|
-
this.capacity = capacity;
|
256
|
-
|
257
|
-
this.initializer = initializer;
|
258
|
-
|
259
|
-
}
|
260
|
-
|
261
|
-
@Override public V get(K key){
|
262
|
-
|
263
|
-
long stamp = lock.writeLock();
|
264
|
-
|
265
|
-
try{ return map.computeIfAbsent(key, initializer); } finally{ lock.unlockWrite(stamp); }
|
266
|
-
|
267
|
-
}
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
}
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
// daisuke7さんに教えていただいたbarakbさんのライブラリ https://github.com/barakb/Cache
|
276
|
-
|
277
|
-
class LruCache3<K, V> implements Calculator<K, V>{
|
278
|
-
|
279
|
-
private Cache<K, V> cache;
|
280
|
-
|
281
|
-
LruCache3(int capacity, Function<K, V> initializer){ cache = new Cache<>(initializer::apply, capacity); }
|
282
|
-
|
283
|
-
@Override public V get(K key){
|
284
|
-
|
285
|
-
try{ return cache.get(key); } catch(Throwable throwable){ throw new RuntimeException(throwable); }
|
286
|
-
|
287
|
-
}
|
288
|
-
|
289
289
|
|
290
290
|
|
291
291
|
}
|
@@ -318,87 +318,87 @@
|
|
318
318
|
|
319
319
|
|
320
320
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
}
|
396
|
-
|
397
|
-
```
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
### 速度計測結果
|
321
|
+
public static final int COST = 1000; // 計算コスト
|
322
|
+
|
323
|
+
public static final int SIZE = 1000; // 要素数
|
324
|
+
|
325
|
+
public static final double DUPLICATION_RATE = 0.0; // 重複率
|
326
|
+
|
327
|
+
public static final double CAPACITY_RATE = 1.0; // 容量率
|
328
|
+
|
329
|
+
public static final int CAPACITY = (int) (Math.round(SIZE * (1.0 - DUPLICATION_RATE) * CAPACITY_RATE)); // 容量
|
330
|
+
|
331
|
+
public static final List<Integer> LIST = IntStream.rangeClosed(0, SIZE - 1).boxed()
|
332
|
+
|
333
|
+
.map(it -> (int) ((double) it * (1.0 - DUPLICATION_RATE))).collect(Collectors.toList());
|
334
|
+
|
335
|
+
public static final int UNIQUE = (int) LIST.stream().distinct().count(); // ユニーク数
|
336
|
+
|
337
|
+
|
338
|
+
|
339
|
+
public static void main(String... args){
|
340
|
+
|
341
|
+
Function<Integer, Integer> calc = it -> it + IntStream.rangeClosed(1, COST * 10000).sum();
|
342
|
+
|
343
|
+
Collections.shuffle(LIST); // リストをシャッフル
|
344
|
+
|
345
|
+
System.out.println("計算コスト:" + COST + " 要素数:" + SIZE + " ユニーク数:" + UNIQUE + " 重複率:" + DUPLICATION_RATE + " 容量:" + CAPACITY + " 容量率:" + CAPACITY_RATE);
|
346
|
+
|
347
|
+
seri("キャッシュなし", new NonCache<>(calc));
|
348
|
+
|
349
|
+
para("キャッシュなし", new NonCache<>(calc));
|
350
|
+
|
351
|
+
seri("LRUなし", new NonLruCache<>(CAPACITY, calc));
|
352
|
+
|
353
|
+
para("LRUなし", new NonLruCache<>(CAPACITY, calc));
|
354
|
+
|
355
|
+
seri("LRUキャッシュ1", new LruCache1(CAPACITY, calc));
|
356
|
+
|
357
|
+
para("LRUキャッシュ1", new LruCache1(CAPACITY, calc));
|
358
|
+
|
359
|
+
seri("LRUキャッシュ2", new LruCache2(CAPACITY, calc));
|
360
|
+
|
361
|
+
para("LRUキャッシュ2", new LruCache2(CAPACITY, calc));
|
362
|
+
|
363
|
+
seri("barakbさんのライブラリ", new LruCache3(CAPACITY, calc));
|
364
|
+
|
365
|
+
para("barakbさんのライブラリ", new LruCache3(CAPACITY, calc));
|
366
|
+
|
367
|
+
}
|
368
|
+
|
369
|
+
|
370
|
+
|
371
|
+
private static void seri(String name, Calculator<Integer, Integer> calculator){
|
372
|
+
|
373
|
+
long start = System.currentTimeMillis();
|
374
|
+
|
375
|
+
LIST.stream().mapToInt(calculator::get).sum();
|
376
|
+
|
377
|
+
System.out.println(name + " シリアル " + (System.currentTimeMillis() - start) + "ms. ");
|
378
|
+
|
379
|
+
}
|
380
|
+
|
381
|
+
|
382
|
+
|
383
|
+
private static void para(String name, Calculator<Integer, Integer> calculator){
|
384
|
+
|
385
|
+
long start = System.currentTimeMillis();
|
386
|
+
|
387
|
+
LIST.parallelStream().mapToInt(calculator::get).sum();
|
388
|
+
|
389
|
+
System.out.println(name + " パラレル " + (System.currentTimeMillis() - start) + "ms. ");
|
390
|
+
|
391
|
+
}
|
392
|
+
|
393
|
+
|
394
|
+
|
395
|
+
}
|
396
|
+
|
397
|
+
```
|
398
|
+
|
399
|
+
|
400
|
+
|
401
|
+
### ■速度計測結果
|
402
402
|
|
403
403
|
|
404
404
|
|
@@ -406,7 +406,7 @@
|
|
406
406
|
|
407
407
|
→「キャッシュなしのパラレル」が当然最速。「LRUなしのパラレル」も排他がないため高速。
|
408
408
|
|
409
|
-
→**「barakbさんのライブラリ
|
409
|
+
→**「barakbさんのライブラリのパラレル」も高速。すごい。**
|
410
410
|
|
411
411
|
|
412
412
|
|
@@ -440,7 +440,7 @@
|
|
440
440
|
|
441
441
|
### 2.2割重複。キャッシュあふれなし。
|
442
442
|
|
443
|
-
→当然、「キャッシュなし」以外すべて
|
443
|
+
→当然、「キャッシュなし」以外すべて速くなる。
|
444
444
|
|
445
445
|
|
446
446
|
|
@@ -474,9 +474,9 @@
|
|
474
474
|
|
475
475
|
#### 3.8割重複。キャッシュあふれなし。
|
476
476
|
|
477
|
-
→すごく
|
477
|
+
→すごく速くなる。キャッシュの効果が大きい。
|
478
|
-
|
478
|
+
|
479
|
-
→「barakbさんのライブラリもパラレル」があいかわらずすごい。
|
479
|
+
→**「barakbさんのライブラリもパラレル」があいかわらずすごい。**
|
480
480
|
|
481
481
|
|
482
482
|
|
@@ -510,9 +510,9 @@
|
|
510
510
|
|
511
511
|
#### 4.2割重複。キャッシュあふれあり。
|
512
512
|
|
513
|
-
→LRUがあると当然、やや遅くなる。
|
513
|
+
→LRUでの削除があると当然、やや遅くなる。
|
514
|
-
|
514
|
+
|
515
|
-
→**それでも「barakbさんのライブラリもパラレル」が速い。**
|
515
|
+
→**それでも「barakbさんのライブラリもパラレル」が速い。** → 「LRUなしのパラレル」に迫る勢い。
|
516
516
|
|
517
517
|
|
518
518
|
|
5
Javaでの速度計測を追記
test
CHANGED
File without changes
|
test
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
|
6
6
|
|
7
|
-
### 要件
|
7
|
+
### ■要件
|
8
8
|
|
9
9
|
|
10
10
|
|
@@ -32,7 +32,7 @@
|
|
32
32
|
|
33
33
|
|
34
34
|
|
35
|
-
### 質問
|
35
|
+
### ■質問
|
36
36
|
|
37
37
|
|
38
38
|
|
@@ -52,88 +52,42 @@
|
|
52
52
|
|
53
53
|
|
54
54
|
|
55
|
-
### ソースコード
|
55
|
+
### ■ソースコード (ご指摘により一部修正)
|
56
56
|
|
57
57
|
|
58
58
|
|
59
59
|
```Kotlin
|
60
60
|
|
61
|
+
import java.util.*
|
62
|
+
|
61
63
|
class LruCache<K, V>(val capacity: Int, private val initializer: (K) -> V) {
|
62
64
|
|
63
|
-
|
64
|
-
|
65
|
-
private val map = Collections.synchronizedMap(object : LinkedHashMap<K, V>(capacity, 1.0F, true) {
|
65
|
+
private val map = java.util.Collections.synchronizedMap(object : LinkedHashMap<K, V>(capacity, 1.0F, true) {
|
66
|
-
|
66
|
+
|
67
|
-
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?)
|
67
|
+
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?) = size > capacity
|
68
|
-
|
69
|
-
return size > capacity
|
70
|
-
|
71
|
-
}
|
72
68
|
|
73
69
|
})
|
74
70
|
|
75
|
-
|
76
|
-
|
77
|
-
operator fun get(key: K): V {
|
78
|
-
|
79
|
-
return map.getOrPut(key) {
|
80
|
-
|
81
|
-
|
71
|
+
operator fun get(key: K): V = map.computeIfAbsent(key, initializer)
|
82
|
-
|
83
|
-
return map[key]!!
|
84
|
-
|
85
|
-
}
|
86
|
-
|
87
|
-
}
|
88
|
-
|
89
|
-
|
90
72
|
|
91
73
|
override fun toString() = map.toString()
|
92
74
|
|
93
|
-
|
94
|
-
|
95
|
-
}
|
75
|
+
}
|
96
|
-
|
76
|
+
|
97
|
-
```
|
77
|
+
```
|
98
|
-
|
99
|
-
|
100
|
-
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
|
101
|
-
###
|
85
|
+
### ■使用例
|
102
86
|
|
103
87
|
|
104
88
|
|
105
89
|
```Kotlin
|
106
90
|
|
107
|
-
import java.util.*
|
108
|
-
|
109
|
-
class LruCache<K, V>(val capacity: Int, private val initializer: (K) -> V) {
|
110
|
-
|
111
|
-
private val map = java.util.Collections.synchronizedMap(object : LinkedHashMap<K, V>(capacity, 1.0F, true) {
|
112
|
-
|
113
|
-
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?) = size > capacity
|
114
|
-
|
115
|
-
})
|
116
|
-
|
117
|
-
operator fun get(key: K): V = map.computeIfAbsent(key, initializer)
|
118
|
-
|
119
|
-
override fun toString() = map.toString()
|
120
|
-
|
121
|
-
}
|
122
|
-
|
123
|
-
```
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
### 使用例
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
```Kotlin
|
136
|
-
|
137
91
|
// 容量が3で2倍の値を返し、計算結果をmemoizeするキャッシュ
|
138
92
|
|
139
93
|
val cache = LruCache<Int, Int>(capacity=3) { it * 2 }
|
@@ -148,7 +102,7 @@
|
|
148
102
|
|
149
103
|
|
150
104
|
|
151
|
-
### 実行結果
|
105
|
+
### ■実行結果
|
152
106
|
|
153
107
|
|
154
108
|
|
@@ -162,96 +116,428 @@
|
|
162
116
|
|
163
117
|
|
164
118
|
|
165
|
-
|
166
|
-
|
167
119
|
---
|
168
120
|
|
169
121
|
|
170
122
|
|
123
|
+
### ========追記========
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
---
|
128
|
+
|
129
|
+
|
130
|
+
|
171
|
-
###
|
131
|
+
### ■速度計測
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
132
|
+
|
133
|
+
|
134
|
+
|
177
|
-
|
135
|
+
キャッシュクラスを5種類用意。(今回はJavaで)
|
136
|
+
|
178
|
-
|
137
|
+
* キャッシュなし
|
138
|
+
|
179
|
-
|
139
|
+
* LRUしないキャッシュ
|
140
|
+
|
180
|
-
|
141
|
+
* Collections.syncronizedMap() で排他するLRUキャッシュ
|
142
|
+
|
143
|
+
* StampedLock.writeLock() で排他するLRUキャッシュ
|
144
|
+
|
145
|
+
* daisuke7さんに教えていただいた[barakbさんのライブラリ](https://github.com/barakb/Cache)
|
146
|
+
|
147
|
+
|
148
|
+
|
181
|
-
```
|
149
|
+
```Java
|
150
|
+
|
151
|
+
import org.async.utils.cache.Cache;
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
import java.util.Collections;
|
156
|
+
|
157
|
+
import java.util.LinkedHashMap;
|
158
|
+
|
159
|
+
import java.util.Map;
|
160
|
+
|
161
|
+
import java.util.concurrent.locks.StampedLock;
|
162
|
+
|
163
|
+
import java.util.function.Function;
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
interface Calculator<K, V>{ V get(K key); }
|
168
|
+
|
169
|
+
|
182
170
|
|
183
171
|
// キャッシュなし
|
184
172
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
}
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
pri
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
173
|
+
class NonCache<K, V> implements Calculator<K, V>{
|
174
|
+
|
175
|
+
private Function<K, V> initializer;
|
176
|
+
|
177
|
+
NonCache(Function<K, V> initializer){ this.initializer = initializer; }
|
178
|
+
|
179
|
+
@Override public V get(K key){ return initializer.apply(key); }
|
180
|
+
|
181
|
+
}
|
182
|
+
|
183
|
+
|
184
|
+
|
185
|
+
// LRUしないキャッシュ
|
186
|
+
|
187
|
+
class NonLruCache<K, V> implements Calculator<K, V>{
|
188
|
+
|
189
|
+
private int capacity;
|
190
|
+
|
191
|
+
private Function<K, V> initializer;
|
192
|
+
|
193
|
+
private Map<K, V> map = new LinkedHashMap<>(capacity, 1.0f, true);
|
194
|
+
|
195
|
+
NonLruCache(int capacity, Function<K, V> initializer){
|
196
|
+
|
197
|
+
this.capacity = capacity;
|
198
|
+
|
199
|
+
this.initializer = initializer;
|
200
|
+
|
201
|
+
}
|
202
|
+
|
203
|
+
@Override public V get(K key){ return map.computeIfAbsent(key, initializer); }
|
204
|
+
|
205
|
+
}
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
// Collections.syncronizedMap() で排他するLRUキャッシュ
|
210
|
+
|
211
|
+
class LruCache1<K, V> implements Calculator<K, V>{
|
212
|
+
|
213
|
+
private int capacity;
|
214
|
+
|
215
|
+
private Function<K, V> initializer;
|
216
|
+
|
217
|
+
private Map<K, V> map = Collections.synchronizedMap(new LinkedHashMap<K, V>(capacity, 1.0f, true){
|
218
|
+
|
219
|
+
@Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest){ return size() > capacity; }
|
220
|
+
|
221
|
+
});
|
222
|
+
|
223
|
+
LruCache1(int capacity, Function<K, V> initializer){
|
224
|
+
|
225
|
+
this.capacity = capacity;
|
226
|
+
|
227
|
+
this.initializer = initializer;
|
228
|
+
|
229
|
+
}
|
230
|
+
|
231
|
+
@Override public V get(K key){ return map.computeIfAbsent(key, initializer); }
|
232
|
+
|
233
|
+
}
|
234
|
+
|
235
|
+
|
236
|
+
|
237
|
+
// StampedLock.writeLock() で排他するLRUキャッシュ
|
238
|
+
|
239
|
+
class LruCache2<K, V> implements Calculator<K, V>{
|
240
|
+
|
241
|
+
private int capacity;
|
242
|
+
|
243
|
+
private StampedLock lock = new StampedLock();
|
244
|
+
|
245
|
+
private Function<K, V> initializer;
|
246
|
+
|
247
|
+
private Map<K, V> map = new LinkedHashMap<K, V>(capacity, 1.0f, true){
|
248
|
+
|
249
|
+
@Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest){ return size() > capacity; }
|
250
|
+
|
251
|
+
};
|
252
|
+
|
253
|
+
LruCache2(int capacity, Function<K, V> initializer){
|
254
|
+
|
255
|
+
this.capacity = capacity;
|
256
|
+
|
257
|
+
this.initializer = initializer;
|
258
|
+
|
259
|
+
}
|
260
|
+
|
261
|
+
@Override public V get(K key){
|
262
|
+
|
263
|
+
long stamp = lock.writeLock();
|
264
|
+
|
265
|
+
try{ return map.computeIfAbsent(key, initializer); } finally{ lock.unlockWrite(stamp); }
|
266
|
+
|
267
|
+
}
|
268
|
+
|
269
|
+
|
270
|
+
|
271
|
+
}
|
272
|
+
|
273
|
+
|
274
|
+
|
275
|
+
// daisuke7さんに教えていただいたbarakbさんのライブラリ https://github.com/barakb/Cache
|
276
|
+
|
277
|
+
class LruCache3<K, V> implements Calculator<K, V>{
|
278
|
+
|
279
|
+
private Cache<K, V> cache;
|
280
|
+
|
281
|
+
LruCache3(int capacity, Function<K, V> initializer){ cache = new Cache<>(initializer::apply, capacity); }
|
282
|
+
|
283
|
+
@Override public V get(K key){
|
284
|
+
|
285
|
+
try{ return cache.get(key); } catch(Throwable throwable){ throw new RuntimeException(throwable); }
|
286
|
+
|
287
|
+
}
|
288
|
+
|
289
|
+
|
290
|
+
|
291
|
+
}
|
292
|
+
|
293
|
+
```
|
294
|
+
|
295
|
+
|
296
|
+
|
297
|
+
以下のプログラムで実行。(メモリやGCに注意が必要)
|
298
|
+
|
299
|
+
|
300
|
+
|
301
|
+
|
302
|
+
|
303
|
+
```Java
|
304
|
+
|
305
|
+
import java.util.Collections;
|
306
|
+
|
307
|
+
import java.util.List;
|
308
|
+
|
309
|
+
import java.util.function.Function;
|
310
|
+
|
311
|
+
import java.util.stream.Collectors;
|
312
|
+
|
313
|
+
import java.util.stream.IntStream;
|
314
|
+
|
315
|
+
|
316
|
+
|
317
|
+
public class LruCacheTest{
|
318
|
+
|
319
|
+
|
320
|
+
|
321
|
+
public static final int COST = 1000; // 計算コスト
|
322
|
+
|
323
|
+
public static final int SIZE = 1000; // 要素数
|
324
|
+
|
325
|
+
public static final double DUPLICATION_RATE = 0.0; // 重複率
|
326
|
+
|
327
|
+
public static final double CAPACITY_RATE = 1.0; // 容量率
|
328
|
+
|
329
|
+
public static final int CAPACITY = (int) (Math.round(SIZE * (1.0 - DUPLICATION_RATE) * CAPACITY_RATE)); // 容量
|
330
|
+
|
331
|
+
public static final List<Integer> LIST = IntStream.rangeClosed(0, SIZE - 1).boxed()
|
332
|
+
|
333
|
+
.map(it -> (int) ((double) it * (1.0 - DUPLICATION_RATE))).collect(Collectors.toList());
|
334
|
+
|
335
|
+
public static final int UNIQUE = (int) LIST.stream().distinct().count(); // ユニーク数
|
336
|
+
|
337
|
+
|
338
|
+
|
339
|
+
public static void main(String... args){
|
340
|
+
|
341
|
+
Function<Integer, Integer> calc = it -> it + IntStream.rangeClosed(1, COST * 10000).sum();
|
342
|
+
|
343
|
+
Collections.shuffle(LIST); // リストをシャッフル
|
344
|
+
|
345
|
+
System.out.println("計算コスト:" + COST + " 要素数:" + SIZE + " ユニーク数:" + UNIQUE + " 重複率:" + DUPLICATION_RATE + " 容量:" + CAPACITY + " 容量率:" + CAPACITY_RATE);
|
346
|
+
|
347
|
+
seri("キャッシュなし", new NonCache<>(calc));
|
348
|
+
|
349
|
+
para("キャッシュなし", new NonCache<>(calc));
|
350
|
+
|
351
|
+
seri("LRUなし", new NonLruCache<>(CAPACITY, calc));
|
352
|
+
|
353
|
+
para("LRUなし", new NonLruCache<>(CAPACITY, calc));
|
354
|
+
|
355
|
+
seri("LRUキャッシュ1", new LruCache1(CAPACITY, calc));
|
356
|
+
|
357
|
+
para("LRUキャッシュ1", new LruCache1(CAPACITY, calc));
|
358
|
+
|
359
|
+
seri("LRUキャッシュ2", new LruCache2(CAPACITY, calc));
|
360
|
+
|
361
|
+
para("LRUキャッシュ2", new LruCache2(CAPACITY, calc));
|
362
|
+
|
363
|
+
seri("barakbさんのライブラリ", new LruCache3(CAPACITY, calc));
|
364
|
+
|
365
|
+
para("barakbさんのライブラリ", new LruCache3(CAPACITY, calc));
|
366
|
+
|
367
|
+
}
|
368
|
+
|
369
|
+
|
370
|
+
|
371
|
+
private static void seri(String name, Calculator<Integer, Integer> calculator){
|
372
|
+
|
373
|
+
long start = System.currentTimeMillis();
|
374
|
+
|
375
|
+
LIST.stream().mapToInt(calculator::get).sum();
|
376
|
+
|
377
|
+
System.out.println(name + " シリアル " + (System.currentTimeMillis() - start) + "ms. ");
|
378
|
+
|
379
|
+
}
|
380
|
+
|
381
|
+
|
382
|
+
|
383
|
+
private static void para(String name, Calculator<Integer, Integer> calculator){
|
384
|
+
|
385
|
+
long start = System.currentTimeMillis();
|
386
|
+
|
387
|
+
LIST.parallelStream().mapToInt(calculator::get).sum();
|
388
|
+
|
389
|
+
System.out.println(name + " パラレル " + (System.currentTimeMillis() - start) + "ms. ");
|
390
|
+
|
391
|
+
}
|
392
|
+
|
393
|
+
|
394
|
+
|
395
|
+
}
|
396
|
+
|
397
|
+
```
|
398
|
+
|
399
|
+
|
400
|
+
|
401
|
+
### 速度計測結果
|
402
|
+
|
403
|
+
|
404
|
+
|
405
|
+
#### 1.重複なし。キャッシュあふれなし。
|
406
|
+
|
407
|
+
→「キャッシュなしのパラレル」が当然最速。「LRUなしのパラレル」も排他がないため高速。
|
408
|
+
|
409
|
+
→**「barakbさんのライブラリもパラレル」も高速。すごい。**
|
410
|
+
|
411
|
+
|
412
|
+
|
413
|
+
```
|
414
|
+
|
415
|
+
計算コスト:1000 要素数:1000 ユニーク数:1000 重複率:0.0 容量:1000 容量率:1.0
|
416
|
+
|
417
|
+
キャッシュなし シリアル 3000ms.
|
418
|
+
|
419
|
+
キャッシュなし パラレル 519ms.
|
420
|
+
|
421
|
+
LRUなし シリアル 2998ms.
|
422
|
+
|
423
|
+
LRUなし パラレル 530ms.
|
424
|
+
|
425
|
+
LRUキャッシュ1 シリアル 3004ms.
|
426
|
+
|
427
|
+
LRUキャッシュ1 パラレル 3030ms.
|
428
|
+
|
429
|
+
LRUキャッシュ2 シリアル 3009ms.
|
430
|
+
|
431
|
+
LRUキャッシュ2 パラレル 3029ms.
|
432
|
+
|
433
|
+
barakbさんのライブラリ シリアル 3007ms.
|
434
|
+
|
435
|
+
barakbさんのライブラリ パラレル 514ms.
|
436
|
+
|
437
|
+
```
|
438
|
+
|
439
|
+
|
440
|
+
|
441
|
+
### 2.2割重複。キャッシュあふれなし。
|
442
|
+
|
443
|
+
→当然、「キャッシュなし」以外すべて早くなる。
|
444
|
+
|
445
|
+
|
446
|
+
|
447
|
+
```
|
448
|
+
|
449
|
+
計算コスト:1000 要素数:1000 ユニーク数:800 重複率:0.2 容量:800 容量率:1.0
|
450
|
+
|
451
|
+
キャッシュなし シリアル 2958ms.
|
452
|
+
|
453
|
+
キャッシュなし パラレル 517ms.
|
454
|
+
|
455
|
+
LRUなし シリアル 2369ms.
|
456
|
+
|
457
|
+
LRUなし パラレル 471ms.
|
458
|
+
|
459
|
+
LRUキャッシュ1 シリアル 2358ms.
|
460
|
+
|
461
|
+
LRUキャッシュ1 パラレル 2393ms.
|
462
|
+
|
463
|
+
LRUキャッシュ2 シリアル 2365ms.
|
464
|
+
|
465
|
+
LRUキャッシュ2 パラレル 2382ms.
|
466
|
+
|
467
|
+
barakbさんのライブラリ シリアル 2351ms.
|
468
|
+
|
469
|
+
barakbさんのライブラリ パラレル 389ms.
|
470
|
+
|
471
|
+
```
|
472
|
+
|
473
|
+
|
474
|
+
|
475
|
+
#### 3.8割重複。キャッシュあふれなし。
|
476
|
+
|
477
|
+
→すごく早くなる。キャッシュの効果が大きい。
|
478
|
+
|
479
|
+
→「barakbさんのライブラリもパラレル」があいかわらずすごい。
|
480
|
+
|
481
|
+
|
482
|
+
|
483
|
+
```
|
484
|
+
|
485
|
+
計算コスト:1000 要素数:1000 ユニーク数:200 重複率:0.8 容量:200 容量率:1.0
|
486
|
+
|
487
|
+
キャッシュなし シリアル 3028ms.
|
488
|
+
|
489
|
+
キャッシュなし パラレル 525ms.
|
490
|
+
|
491
|
+
LRUなし シリアル 596ms.
|
492
|
+
|
493
|
+
LRUなし パラレル 136ms.
|
494
|
+
|
495
|
+
LRUキャッシュ1 シリアル 615ms.
|
496
|
+
|
497
|
+
LRUキャッシュ1 パラレル 624ms.
|
498
|
+
|
499
|
+
LRUキャッシュ2 シリアル 611ms.
|
500
|
+
|
501
|
+
LRUキャッシュ2 パラレル 614ms.
|
502
|
+
|
503
|
+
barakbさんのライブラリ シリアル 595ms.
|
504
|
+
|
505
|
+
barakbさんのライブラリ パラレル 99ms.
|
506
|
+
|
507
|
+
```
|
508
|
+
|
509
|
+
|
510
|
+
|
511
|
+
#### 4.2割重複。キャッシュあふれあり。
|
512
|
+
|
513
|
+
→LRUがあると当然、やや遅くなる。
|
514
|
+
|
515
|
+
→**それでも「barakbさんのライブラリもパラレル」が速い。**
|
516
|
+
|
517
|
+
|
518
|
+
|
519
|
+
```
|
520
|
+
|
521
|
+
計算コスト:1000 要素数:1000 ユニーク数:800 重複率:0.2 容量:400 容量率:0.5
|
522
|
+
|
523
|
+
キャッシュなし シリアル 2965ms.
|
524
|
+
|
525
|
+
キャッシュなし パラレル 516ms.
|
526
|
+
|
527
|
+
LRUなし シリアル 2381ms.
|
528
|
+
|
529
|
+
LRUなし パラレル 413ms.
|
530
|
+
|
531
|
+
LRUキャッシュ1 シリアル 2546ms.
|
532
|
+
|
533
|
+
LRUキャッシュ1 パラレル 2577ms.
|
534
|
+
|
535
|
+
LRUキャッシュ2 シリアル 2549ms.
|
536
|
+
|
537
|
+
LRUキャッシュ2 パラレル 2521ms.
|
538
|
+
|
539
|
+
barakbさんのライブラリ シリアル 2525ms.
|
540
|
+
|
541
|
+
barakbさんのライブラリ パラレル 456ms.
|
542
|
+
|
543
|
+
```
|
4
誤記修正
test
CHANGED
File without changes
|
test
CHANGED
@@ -114,7 +114,7 @@
|
|
114
114
|
|
115
115
|
})
|
116
116
|
|
117
|
-
o
|
117
|
+
operator fun get(key: K): V = map.computeIfAbsent(key, initializer)
|
118
118
|
|
119
119
|
override fun toString() = map.toString()
|
120
120
|
|
3
コメントでのご指摘を受けての修正
test
CHANGED
File without changes
|
test
CHANGED
@@ -104,17 +104,19 @@
|
|
104
104
|
|
105
105
|
```Kotlin
|
106
106
|
|
107
|
+
import java.util.*
|
108
|
+
|
107
109
|
class LruCache<K, V>(val capacity: Int, private val initializer: (K) -> V) {
|
108
110
|
|
109
|
-
|
111
|
+
private val map = java.util.Collections.synchronizedMap(object : LinkedHashMap<K, V>(capacity, 1.0F, true) {
|
110
|
-
|
112
|
+
|
111
|
-
|
113
|
+
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?) = size > capacity
|
112
|
-
|
114
|
+
|
113
|
-
|
115
|
+
})
|
114
|
-
|
116
|
+
|
115
|
-
|
117
|
+
override operator fun get(key: K): V = map.computeIfAbsent(key, initializer)
|
116
|
-
|
118
|
+
|
117
|
-
|
119
|
+
override fun toString() = map.toString()
|
118
120
|
|
119
121
|
}
|
120
122
|
|
2
ご指摘を受けての修正
test
CHANGED
File without changes
|
test
CHANGED
@@ -52,7 +52,7 @@
|
|
52
52
|
|
53
53
|
|
54
54
|
|
55
|
-
### ソースコード
|
55
|
+
### ソースコード →バグあり
|
56
56
|
|
57
57
|
|
58
58
|
|
@@ -98,6 +98,30 @@
|
|
98
98
|
|
99
99
|
|
100
100
|
|
101
|
+
### ご指摘を受けての修正版ソースコード
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
```Kotlin
|
106
|
+
|
107
|
+
class LruCache<K, V>(val capacity: Int, private val initializer: (K) -> V) {
|
108
|
+
|
109
|
+
private val map = Collections.synchronizedMap(object : LinkedHashMap<K, V>(capacity, 1.0F, true) {
|
110
|
+
|
111
|
+
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?) = size > capacity
|
112
|
+
|
113
|
+
})
|
114
|
+
|
115
|
+
operator fun get(key: K): V = map.getOrPut(key) { initializer(key) }
|
116
|
+
|
117
|
+
override fun toString() = map.toString()
|
118
|
+
|
119
|
+
}
|
120
|
+
|
121
|
+
```
|
122
|
+
|
123
|
+
|
124
|
+
|
101
125
|
|
102
126
|
|
103
127
|
|
1
速度などを追記
test
CHANGED
File without changes
|
test
CHANGED
@@ -133,3 +133,99 @@
|
|
133
133
|
{2=4, 3=6, 4=8}
|
134
134
|
|
135
135
|
```
|
136
|
+
|
137
|
+
|
138
|
+
|
139
|
+
|
140
|
+
|
141
|
+
---
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
### ここから追記
|
146
|
+
|
147
|
+
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
##### **■キャッシュなしの実装** の速度
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
```Kotlin
|
156
|
+
|
157
|
+
// キャッシュなし
|
158
|
+
|
159
|
+
val cache = object {
|
160
|
+
|
161
|
+
operator fun get(key: Int) = Math.sin(key.toDouble()) * Math.cos(key.toDouble()) * Math.tan(key.toDouble())
|
162
|
+
|
163
|
+
}
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
// マルチスレッドでアクセス
|
168
|
+
|
169
|
+
val executorService = Executors.newFixedThreadPool(8)
|
170
|
+
|
171
|
+
val start = System.currentTimeMillis()
|
172
|
+
|
173
|
+
(1..10000000).forEach { cache[it / 3] }
|
174
|
+
|
175
|
+
executorService.shutdown()
|
176
|
+
|
177
|
+
println((System.currentTimeMillis() - start).toString() + "ms")
|
178
|
+
|
179
|
+
```
|
180
|
+
|
181
|
+
|
182
|
+
|
183
|
+
→10回処理した結果 **平均:7286.2ms** ```7452ms, 7126ms, 7296ms, 7287ms, 7276ms, 7281ms, 7298ms, 7277ms, 7295ms, 7274ms```
|
184
|
+
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
|
189
|
+
|
190
|
+
|
191
|
+
##### **■質問者の実装** の速度
|
192
|
+
|
193
|
+
|
194
|
+
|
195
|
+
```Kotlin
|
196
|
+
|
197
|
+
val cache = LruCache<Int, Double>(10000) { Math.sin(it.toDouble()) * Math.cos(it.toDouble()) * Math.tan(it.toDouble()) }
|
198
|
+
|
199
|
+
// あとは一緒
|
200
|
+
|
201
|
+
```
|
202
|
+
|
203
|
+
|
204
|
+
|
205
|
+
→10回処理した結果 **平均:2772.7ms** ```3239ms, 3069ms, 2702ms, 2666ms, 2677ms, 2667ms, 2675ms, 2677ms, 2690ms, 2665ms```
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
|
210
|
+
|
211
|
+
|
212
|
+
|
213
|
+
|
214
|
+
|
215
|
+
|
216
|
+
|
217
|
+
##### **■barakbさんのライブラリ** の速度
|
218
|
+
|
219
|
+
|
220
|
+
|
221
|
+
```Kotlin
|
222
|
+
|
223
|
+
val cache = Cache<Int, Double>({ Math.sin(it.toDouble()) * Math.cos(it.toDouble()) * Math.tan(it.toDouble()) }, 10000)
|
224
|
+
|
225
|
+
// あとは一緒
|
226
|
+
|
227
|
+
```
|
228
|
+
|
229
|
+
|
230
|
+
|
231
|
+
→10回処理した結果 **平均:3049.1ms** ```3513ms, 3292ms, 3089ms, 2933ms, 2932ms, 2949ms, 2948ms, 2934ms, 2949ms, 2952ms```
|