Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9307eb5
Feat: add FastAtomicIntArray
LinShunKang Feb 15, 2024
bd3774b
Perf: improve FastAtomicIntArray throughput
LinShunKang Feb 16, 2024
d35b287
Feat: improve the code logic of FastAtomicIntArray
LinShunKang Mar 17, 2024
2485459
Refactor: Correct the method name
LinShunKang Apr 29, 2024
c46402f
Feat: introduce FastAtomicIntArrayV1 and FastAtomicIntArrayV2
LinShunKang Apr 29, 2024
37c5fc3
Chore: upgrade minimal supported jdk version to 8
LinShunKang May 19, 2024
f4374c5
Perf: improve all atomic int array throughput
LinShunKang May 19, 2024
dbe73cb
Refactor: introduce AtomicIntArray Interface; remove FastAtomicIntArr…
LinShunKang Jun 16, 2024
32ac683
Merge branch 'master' into feature/4.0
LinShunKang Nov 16, 2025
f93a9d8
Feat: Remove RoughRecorder
LinShunKang Jul 13, 2024
f353b5c
Refactor: clean code
LinShunKang Jul 14, 2024
f4b403d
Refactor: clean code
LinShunKang Jul 14, 2024
82f4aa7
Chore: Upgrade project version to 4.0.0-SNAPSHOT
LinShunKang Nov 16, 2025
54b1a55
Feat: Use long[] as the underlying data structure for AtomicIntHashCo…
LinShunKang Apr 27, 2026
d46ad2c
Merge branch 'develop' into feature/AtomicIntHashCounter
LinShunKang Apr 29, 2026
ccfd0a6
Test: Update AtomicIntHashCounterBench result
LinShunKang Apr 29, 2026
a7b9639
Merge branch 'feature/AtomicIntHashCounter' into feature/4.0
LinShunKang May 1, 2026
cef5ed8
Refactor: Clean codes
LinShunKang May 2, 2026
28d5b19
Refactor: Clean codes
LinShunKang May 2, 2026
ac77e9d
Perf: Simple optimize AtomicIntHashCounter
LinShunKang May 3, 2026
150850a
Feat: Refine the code logic for AtomicIntHashCounter
LinShunKang May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Feat: improve the code logic of FastAtomicIntArray
  • Loading branch information
LinShunKang committed Nov 8, 2025
commit d35b287c2d79922616b4407743923be2ca5b8d2b
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,43 @@ public final class FastAtomicIntArray implements Serializable {
private static final int base = Unsafe.ARRAY_INT_BASE_OFFSET;
private static final int scale = Unsafe.ARRAY_INT_INDEX_SCALE;
private static final int shift = 31 - Integer.numberOfLeadingZeros(scale);
private static final int falseSharingShift = 31 - Integer.numberOfLeadingZeros(64 / scale);

static {
if ((scale & (scale - 1)) != 0) {
if (!isPowerOfTwo(scale)) {
throw new Error("data type scale not a power of two");
}
}

private static boolean isPowerOfTwo(int i) {
return (i & (i - 1)) == 0;
}

private static long byteOffset(int i) {
return ((long) i << shift) + base;
}

private final int actualLength;

//TODO:LSK 还可以把二维数组转变为一维数组,通过下标来逻辑分割不同数组!
// 假设需要转换的数组为 int[n][m],那么转换的方式有 2 种:
// 第一种:[A11,A12,A13,....,A1m, A21,A22,A23,....,A2m, An1,....,Anm]
// 第二种:[A11,A21,A31,....,An1, A12,A22,A32,....,An2, A1m,....,Anm]
// 理论上第二种可以更好的利用 CPU 缓存!
private final int[][] arrays;

public FastAtomicIntArray(int length) {
this.actualLength = length << 4;
this.arrays = generateArrays(length);
this(length, 4);
}

public FastAtomicIntArray(int length, int shards) {
if (!isPowerOfTwo(shards)) {
throw new IllegalArgumentException("shards not a power of two");
}

this.actualLength = length << falseSharingShift;
this.arrays = generateArrays(this.actualLength, shards);
}

private int[][] generateArrays(int length) {
// final int[][] arrays = new int[Runtime.getRuntime().availableProcessors()][];
final int[][] arrays = new int[16][]; //TODO:LSK 先给固定值
for (int i = 0; i < arrays.length; i++) {
arrays[i] = new int[length << 4];
private int[][] generateArrays(int actualLength, int shards) {
final int[][] arrays = new int[shards][];
for (int i = 0; i < shards; i++) {
arrays[i] = new int[actualLength];
}
return arrays;
}
Expand All @@ -59,12 +66,12 @@ private long checkedByteOffset(int i) {
}

public int length() {
return this.actualLength >> 4;
return this.actualLength >> falseSharingShift;
}

public int get(int i) {
int result = 0;
final long byteOffset = checkedByteOffset(i << 4);
final long byteOffset = checkedByteOffset(i << falseSharingShift);
for (int[] array : arrays) {
result += getRaw(array, byteOffset);
}
Expand All @@ -75,14 +82,18 @@ private int getRaw(int[] array, long offset) {
return unsafe.getIntVolatile(array, offset);
}

public boolean increment(int i) {
public int incrementAndGet(int i) {
return addAndGet(i, 1);
}

public int addAndGet(int i, int delta) {
final int[][] arrays = this.arrays;
final int n = arrays.length;
final int[] array = arrays[(n - 1) & hash(Thread.currentThread().getId())];
final long byteOffset = checkedByteOffset(i << 4);
final int[] array = arrays[(arrays.length - 1) & hash(Thread.currentThread().getId())];
final long byteOffset = checkedByteOffset(i << falseSharingShift);
while (true) {
if (cas(array, byteOffset, 1)) {
return true;
final int current = getRaw(array, byteOffset);
if (unsafe.compareAndSwapInt(array, byteOffset, current, current + delta)) {
return current;
}
}
}
Expand All @@ -91,11 +102,6 @@ private int hash(long threadId) {
return (int) (threadId >>> 16 ^ threadId);
}

private boolean cas(int[] array, long byteOffset, int delta) {
final int current = getRaw(array, byteOffset);
return unsafe.compareAndSwapInt(array, byteOffset, current, current + delta);
}

public void reset() {
for (int[] array : arrays) {
unsafe.setMemory(array, byteOffset(0), (long) array.length * scale, (byte) 0);
Expand All @@ -104,7 +110,7 @@ public void reset() {

public long fillSortedKvs(LongBuf longBuf) {
long totalCount = 0L;
for (int i = 0, len = this.actualLength << 4; i < len; ++i) {
for (int i = 0, len = length(); i < len; ++i) {
final int count = get(i);
if (count > 0) {
longBuf.write(i, count);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
package cn.myperf4j.base.util.concurrent;

import cn.myperf4j.base.buffer.LongBuf;
import cn.myperf4j.base.util.Logger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicIntegerArray;

import static cn.myperf4j.base.util.NumUtils.parseKey;
import static cn.myperf4j.base.util.NumUtils.parseValue;

/**
* Created by LinShunkang on 2024/02/14
*/
Expand All @@ -20,7 +32,7 @@ public void clear() {
public void testReset() {
final int length = atomicIntArray.length();
for (int i = 0; i < length; i++) {
atomicIntArray.increment(i);
atomicIntArray.incrementAndGet(i);
}

for (int i = 0; i < length; i++) {
Expand All @@ -38,19 +50,181 @@ public void testReset() {
public void testIncrement() {
final int length = atomicIntArray.length();
for (int i = 0; i < length; i++) {
atomicIntArray.increment(i);
atomicIntArray.incrementAndGet(i);
}

for (int i = 0; i < length; i++) {
Assert.assertEquals(1, atomicIntArray.get(i));
}

for (int i = 0; i < length; i++) {
atomicIntArray.increment(i);
atomicIntArray.incrementAndGet(i);
}

for (int i = 0; i < length; i++) {
Assert.assertEquals(2, atomicIntArray.get(i));
}
}

@Test
public void testFillSortedKvs() {
final int length = atomicIntArray.length();
for (int i = 1; i < length; i++) {
atomicIntArray.addAndGet(i, i);
}

for (int i = 0; i < length; i++) {
Assert.assertEquals(i, atomicIntArray.get(i));
}

final LongBuf longBuf = new LongBuf(length);
Assert.assertEquals(((long) length * (length - 1)) / 2, atomicIntArray.fillSortedKvs(longBuf));

for (int i = 0; i < longBuf.writerIndex() - 1; i++) {
final long kv = longBuf.getLong(i);
final int key = parseKey(kv);
final int value = parseValue(kv);
System.out.printf("i=%d key=%d value=%d%n", i, key, value);
Assert.assertEquals(i + 1, key);
Assert.assertEquals(i + 1, value);
}
}

@Test
public void testSingleThread() {
final AtomicIntegerArray intArray = new AtomicIntegerArray(128 * 1024);
final FastAtomicIntArray fastIntArray = new FastAtomicIntArray(128 * 1024);
mode1(intArray, fastIntArray, 1024, 64);
mode1(intArray, fastIntArray, 256, 256);
mode1(intArray, fastIntArray, 64, 1024);
mode1(intArray, fastIntArray, 16, 4 * 1024);
mode1(intArray, fastIntArray, 4, 16 * 1024);
mode1(intArray, fastIntArray, 1, 64 * 1024);

for (int i = 0; i < intArray.length(); i++) {
Assert.assertEquals(intArray.get(i), fastIntArray.get(i));
}
}

private void mode1(AtomicIntegerArray intArray,
FastAtomicIntArray fastIntArray,
int x,
int y) {
final long start = System.nanoTime();
for (int i = 0; i < x; i++) {
for (int j = 1; j < y; j++) {
intArray.incrementAndGet(j);
fastIntArray.incrementAndGet(j);
}
}
Logger.info("x=" + x + ", y=" + y + ", cost=" + (System.nanoTime() - start) / 1000_000 + "ms");
}

@Test
public void testMultiThread4HighRace() throws InterruptedException, BrokenBarrierException {
final int threadCnt = Math.max(Runtime.getRuntime().availableProcessors() - 2, 1);
final ExecutorService executor = Executors.newFixedThreadPool(threadCnt);
int failureTimes = 0;
final int testTimes = 1024 * 1024;
// final int testTimes = 16 * 1024;
final ThreadLocalRandom random = ThreadLocalRandom.current();
for (int i = 0; i < testTimes; i++) {
System.out.printf("--------------------- Round %d start ---------------------\n", i);
final int randomKeyBound = random.nextInt(18, 514);
final int randomDeltaBound = random.nextInt(2, 16);
if (!testMultiThread0(executor, threadCnt, randomKeyBound, randomDeltaBound)) {
failureTimes++;
}
System.out.printf("--------------------- Round %d stop, already has %d failure ---------------------\n\n",
i, failureTimes);
}
System.out.println("testMultiThread4ManyKeys(): failureTimes=" + failureTimes +
", failureRate=" + (1.0D * failureTimes / testTimes));
}

@Test
public void testMultiThread4LowRace() throws InterruptedException, BrokenBarrierException {
final int threadCnt = Math.max(Runtime.getRuntime().availableProcessors() - 2, 1);
final ExecutorService executor = Executors.newFixedThreadPool(threadCnt);
int failureTimes = 0;
final int testTimes = 1024 * 1024;
// final int testTimes = 16 * 1024;
final ThreadLocalRandom random = ThreadLocalRandom.current();
for (int i = 0; i < testTimes; i++) {
System.out.printf("--------------------- Round %d start ---------------------\n", i);
final int randomKeyBound = random.nextInt(514, 1024 * 1024);
final int randomDeltaBound = random.nextInt(2, 16);
if (!testMultiThread0(executor, threadCnt, randomKeyBound, randomDeltaBound)) {
failureTimes++;
}
System.out.printf("--------------------- Round %d stop, already has %d failure ---------------------\n\n",
i, failureTimes);
}
System.out.println("testMultiThread4LowRace(): failureTimes=" + failureTimes +
", failureRate=" + (1.0D * failureTimes / testTimes));
}

private static boolean testMultiThread0(final ExecutorService executor,
final int threadCnt,
final int randomKeyBound,
final int randomDeltaBound)
throws InterruptedException, BrokenBarrierException {
final int testTimes = ThreadLocalRandom.current().nextInt(0, 8 * 1024);
final AtomicIntegerArray intArray = new AtomicIntegerArray(randomKeyBound);
final FastAtomicIntArray fastIntArray = new FastAtomicIntArray(randomKeyBound);
final CyclicBarrier barrier = new CyclicBarrier(threadCnt + 1);
for (int i = 0; i < threadCnt; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}

try {
final ThreadLocalRandom random = ThreadLocalRandom.current();
for (int k = 0; k < testTimes; k++) {
final int randomKey = random.nextInt(0, randomKeyBound);
final int randomDelta = random.nextInt(1, randomDeltaBound);
intArray.addAndGet(randomKey, randomDelta);
fastIntArray.addAndGet(randomKey, randomDelta);
}
} finally {
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
});
}
barrier.await();

final long start = System.nanoTime();
System.out.printf("M starting, threadCnt=%d, testTimes=%d, randomKeyBound=%d, randomDeltaBound=%d\n",
threadCnt, testTimes, randomKeyBound, randomDeltaBound);

barrier.await();
System.out.printf("Cost %dms, length=%d\n", (System.nanoTime() - start) / 1_000_000L, intArray.length());

for (int i = 0; i < intArray.length(); i++) {
Assert.assertEquals(intArray.get(i), fastIntArray.get(i));
}

final LongBuf longBuf = new LongBuf(fastIntArray.length());
fastIntArray.fillSortedKvs(longBuf);
for (int i = 0; i < longBuf.writerIndex(); i++) {
final long kv = longBuf.getLong(i);
final int key = parseKey(kv);
final int value = parseValue(kv);
Assert.assertEquals("k=" + key, intArray.get(key), fastIntArray.get(key));
Assert.assertEquals("k=" + key + ", v=" + value + ", kv=" + kv, intArray.get(key), value);
}

System.out.println("Congratulations!");
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void setup() {
final int length = arrayLength();
jdkIntArray = new AtomicIntegerArray(length);
atomicIntArray = new AtomicIntArray(length);
fastAtomicIntArray = new FastAtomicIntArray(length);
fastAtomicIntArray = new FastAtomicIntArray(length, 16);
}

abstract int arrayLength();
Expand Down
Loading