Skip to content

Commit 45e4b77

Browse files
committed
Optimize last-position sequence lookup
1 parent 4a9c058 commit 45e4b77

2 files changed

Lines changed: 68 additions & 7 deletions

File tree

src/main/java/net/openhft/chronicle/queue/impl/single/SCQIndexing.java

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,8 @@ private ScanResult linearScan0(@NotNull final Wire wire,
505505
fromKnownIndex = lastIndex;
506506
}
507507

508-
bytes.readPositionUnlimited(knownAddress);
508+
bytes.readLimitToCapacity();
509+
bytes.readPosition(knownAddress);
509510

510511
for (long i = fromKnownIndex; ; i++) {
511512
try {
@@ -565,10 +566,26 @@ long linearScanByPosition(@NotNull final Wire wire,
565566
final long indexOfNext,
566567
final long startAddress,
567568
boolean inclusive) throws EOFException {
569+
return linearScanByPosition(wire, toPosition, indexOfNext, startAddress, inclusive, "linearScan by position");
570+
}
571+
572+
/**
573+
* Variant that lets the caller supply a description for the perf log so different scan
574+
* origins (fast path from {@code writePosition}, fall-through from indexed anchor, etc.)
575+
* are distinguishable in logs -- important because the whole point of the
576+
* {@code MAX_VALUE} fast path is to <em>avoid</em> the brute-force fall-through scan, and
577+
* we want to be able to verify that from log output rather than guess.
578+
*/
579+
long linearScanByPosition(@NotNull final Wire wire,
580+
final long toPosition,
581+
final long indexOfNext,
582+
final long startAddress,
583+
boolean inclusive,
584+
String desc) throws EOFException {
568585
long start = REPORT_LINEAR_SCAN ? System.nanoTime() : 0;
569586
long index = linearScanByPosition0(wire, toPosition, indexOfNext, startAddress, inclusive);
570587
if (REPORT_LINEAR_SCAN) {
571-
printLinearScanTime(index, startAddress, start, "linearScan by position");
588+
printLinearScanTime(index, startAddress, start, desc);
572589
}
573590
return index;
574591
}
@@ -664,12 +681,13 @@ long linearScanByPosition0(@NotNull final Wire wire,
664681
* @return The starting index for the scan.
665682
*/
666683
private long calculateInitialValue(long toPosition, long indexOfNext, long startAddress, Bytes<?> bytes, long lastAddress, long lastIndex) {
684+
bytes.readLimit(bytes.capacity());
667685
if (lastAddress > 0 && toPosition == lastAddress
668686
&& lastIndex != Sequence.NOT_FOUND && lastIndex != Sequence.NOT_FOUND_RETRY) {
669-
bytes.readPositionUnlimited(toPosition);
687+
bytes.readPosition(toPosition);
670688
return lastIndex - 1;
671689
} else {
672-
bytes.readPositionUnlimited(startAddress);
690+
bytes.readPosition(startAddress);
673691
return indexOfNext - 1;
674692
}
675693
}
@@ -709,6 +727,44 @@ public long nextEntryToBeIndexed() {
709727
return nextEntryToBeIndexed.getVolatileValue();
710728
}
711729

730+
long sequenceForMaxPosition(@NotNull ExcerptContext ec,
731+
boolean inclusive) throws StreamCorruptedException {
732+
int retry = 0;
733+
for (; retry < 128; retry++) {
734+
long lastWritePos = writePosition.getVolatileValue();
735+
long latestSeq = sequence.getSequence(lastWritePos);
736+
if (latestSeq >= 0) {
737+
if (retry > 0) {
738+
Jvm.perf().on(getClass(),
739+
"sequenceForPosition(MAX_VALUE) tracker-read retried" + " " + retry + " times");
740+
}
741+
try {
742+
return linearScanByPosition(ec.wireForIndex(), Long.MAX_VALUE, latestSeq, lastWritePos, inclusive,
743+
"linearScan from writePosition (sequenceForPosition fast path)");
744+
} catch (EOFException e) {
745+
throw new UncheckedIOException(e);
746+
}
747+
}
748+
if (latestSeq == Sequence.NOT_FOUND)
749+
break;
750+
751+
// NOT_FOUND_RETRY: writer raced. Spin-retry for the first couple of iterations
752+
// (the race window is sub-microsecond); after that yield to let the writer make
753+
// progress instead of starving them with our volatile reads.
754+
if (retry > 2)
755+
Thread.yield();
756+
}
757+
if (retry >= 128) {
758+
// Retry budget exhausted in a row of NOT_FOUND_RETRY -- surface this clearly:
759+
// it means we're about to do a brute-force indexed-anchor scan instead of the
760+
// efficient writePos-anchored fast-path scan. The log makes a regression visible.
761+
Jvm.perf().on(getClass(),
762+
"sequenceForPosition(MAX_VALUE) tracker-read retry loop exhausted" + " after " + retry +
763+
" attempts; falling through to indexed lookup");
764+
}
765+
return sequenceForPosition(ec, Long.MAX_VALUE, inclusive);
766+
}
767+
712768
/**
713769
* Returns the sequence number for a given position in the wire.
714770
* If an exact match is found for the position, the corresponding index is returned;
@@ -773,7 +829,7 @@ long sequenceForPosition(@NotNull ExcerptContext ec,
773829
}
774830
try {
775831
// Perform a linear scan if no exact match is found.
776-
return linearScanByPosition(wire, position, indexOfNext, lastKnownAddress, inclusive);
832+
return linearScanByPosition(wire, position, indexOfNext, lastKnownAddress, inclusive, "linearScan from indexed anchor (sequenceForPosition fall-through)");
777833
} catch (EOFException e) {
778834
throw new UncheckedIOException(e);
779835
}
@@ -976,13 +1032,16 @@ public long lastSequenceNumber(@NotNull ExcerptContext ec)
9761032
break;
9771033
try {
9781034
Wire wireForIndex = ec.wireForIndex();
979-
return wireForIndex == null ? sequence : linearScanByPosition(wireForIndex, Long.MAX_VALUE, sequence, address, true);
1035+
return wireForIndex == null ? sequence : linearScanByPosition(wireForIndex, Long.MAX_VALUE, sequence, address, true,
1036+
"linearScan from writePosition (lastSequenceNumber tail-check)");
9801037
} catch (EOFException e) {
9811038
throw new UncheckedIOException(e);
9821039
}
9831040
}
9841041
}
9851042

1043+
// The write-position retry budget has already been spent above. Fall through
1044+
// directly to the indexed lookup instead of restarting the MAX_VALUE fast path.
9861045
return sequenceForPosition(ec, Long.MAX_VALUE, false);
9871046
}
9881047

src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueStore.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,9 @@ public MappedBytes bytes() {
377377
public long sequenceForPosition(@NotNull final ExcerptContext ec, final long position, boolean inclusive) throws StreamCorruptedException {
378378
throwExceptionIfClosed();
379379

380-
return indexing.sequenceForPosition(ec, position, inclusive);
380+
return position == Long.MAX_VALUE
381+
? indexing.sequenceForMaxPosition(ec, inclusive)
382+
: indexing.sequenceForPosition(ec, position, inclusive);
381383
}
382384

383385
/**

0 commit comments

Comments
 (0)