blob: 3b64d3821696578a356f1b3f466bcd796fc20225 [file] [log] [blame]
nharmatad28af662018-03-27 14:52:44 -07001// Copyright 2018 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.concurrent;
15
16import com.google.common.collect.ImmutableMap;
nharmatad28af662018-03-27 14:52:44 -070017import com.google.common.collect.Maps;
18import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
Googler3687f2a2018-05-02 13:27:28 -070019import java.util.concurrent.ConcurrentHashMap;
nharmatad28af662018-03-27 14:52:44 -070020import java.util.concurrent.ConcurrentMap;
21import java.util.concurrent.atomic.AtomicLong;
22
23/**
24 * A map of atomic long counters. A key whose counter's value is currently zero is _not_
25 * automatically removed from the map; use {@link #clear} to clear the entire map.
26 *
27 * <p>This is very similar to Guava's AtomicLongMap, but optimized for the case where keys are hot,
28 * e.g. a high number of concurrent calls to {@code map.incrementAndGet(k)} and/or
29 * {@code map.decrementAndGet(k)}, for the same key {@code k)}. Guava's AtomicLongMap uses
30 * ConcurrentHashMap#compute, whose implementation unfortunately has internal synchronization even
31 * when there's already an internal entry for the key in question.
32 */
33@ThreadSafe
34public class FastHotKeyAtomicLongMap<T> {
35 private final ConcurrentMap<T, AtomicLong> map;
36
37 public static <T> FastHotKeyAtomicLongMap<T> create() {
Googler3687f2a2018-05-02 13:27:28 -070038 return new FastHotKeyAtomicLongMap<>();
nharmatad28af662018-03-27 14:52:44 -070039 }
40
Googler3687f2a2018-05-02 13:27:28 -070041 // TODO(kak): Delete this in favor of create()
42 public static <T> FastHotKeyAtomicLongMap<T> create(int concurrencyLevel /* ignored */) {
43 return new FastHotKeyAtomicLongMap<>();
nharmatad28af662018-03-27 14:52:44 -070044 }
45
Googler3687f2a2018-05-02 13:27:28 -070046 private FastHotKeyAtomicLongMap() {
47 this.map = new ConcurrentHashMap<>();
nharmatad28af662018-03-27 14:52:44 -070048 }
49
50 public long incrementAndGet(T key) {
nharmata81cc4132018-03-28 15:50:02 -070051 return getCounter(key).incrementAndGet();
nharmatad28af662018-03-27 14:52:44 -070052 }
53
54 public long decrementAndGet(T key) {
nharmata81cc4132018-03-28 15:50:02 -070055 return getCounter(key).decrementAndGet();
nharmatad28af662018-03-27 14:52:44 -070056 }
57
58 public ImmutableMap<T, Long> asImmutableMap() {
59 return ImmutableMap.copyOf(Maps.transformValues(map, AtomicLong::get));
60 }
61
nharmata81cc4132018-03-28 15:50:02 -070062 /**
63 * Returns the {@link AtomicLong} for the given {@code element}. Mutations to this
64 * {@link AtomicLong} will be reflected in the {@link FastHotKeyAtomicLongMap}: for example,
65 * {@code map.getCounter(e).incrementAndGet()} has exactly the same side effects as
66 * {@code map.incrementAndGet(e)}.
67 *
68 * <p>Consider using this method when you have a super-hot key that you know about a priori.
69 * Prefer {@link #incrementAndGet} and {@link #decrementAndGet} otherwise.
70 */
71 public AtomicLong getCounter(T element) {
nharmatad28af662018-03-27 14:52:44 -070072 // Optimize for the case where 'element' is already in our map. See the class javadoc.
73 AtomicLong counter = map.get(element);
74 return counter != null ? counter : map.computeIfAbsent(element, s -> new AtomicLong(0));
75 }
nharmata81cc4132018-03-28 15:50:02 -070076
77 /**
78 * Clears the {@link FastHotKeyAtomicLongMap}.
79 *
80 * <p>Any {@link AtomicLong} instances previously returned by a call to {@link #getCounter} are
81 * now meaningless: mutations to them will not be reflected in the
82 * {@link FastHotKeyAtomicLongMap}.
83 */
84 public void clear() {
85 map.clear();
86 }
nharmatad28af662018-03-27 14:52:44 -070087}