Skip to content
Merged
Changes from 1 commit
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ function isPresent<T>(x: Optional<T>): x is T {
}

interface PredicateInfo {
name: string;
raHash: string;
tuples: number;
evaluationCount: number;
iterationCount: number;
Expand All @@ -43,23 +45,37 @@ interface PredicateInfo {
}

class ComparisonDataset {
/**
* Predicates indexed by a key consisting of the name and its pipeline hash.
* Unlike the RA hash, the pipeline hash only depends on the predicate's own pipeline.
*/
public keyToIndex = new Map<string, number>();
public raToIndex = new Map<string, number>();
public nameToIndex = new Map<string, number>();
public cacheHitIndices: Set<number>;
public sentinelEmptyIndices: Set<number>;

constructor(public data: PerformanceComparisonDataFromLog) {
const { names } = data;
const { nameToIndex } = this;
constructor(private data: PerformanceComparisonDataFromLog) {
const { names, raHashes, pipelineSummaryList } = data;
const { keyToIndex, raToIndex, nameToIndex } = this;
for (let i = 0; i < names.length; i++) {
nameToIndex.set(names[i], i);
const name = names[i];
const pipelineHash = getPipelineSummaryHash(pipelineSummaryList[i]);
keyToIndex.set(`${name}@${pipelineHash}`, i);
nameToIndex.set(name, i);
raToIndex.set(raHashes[i], i);
}
this.cacheHitIndices = new Set(data.cacheHitIndices);
this.sentinelEmptyIndices = new Set(data.sentinelEmptyIndices);
}

getTupleCountInfo(name: string): Optional<PredicateInfo> {
const { data, nameToIndex, cacheHitIndices, sentinelEmptyIndices } = this;
const index = nameToIndex.get(name);
keys() {
return Array.from(this.keyToIndex.keys());
}

getTupleCountInfo(key: string): Optional<PredicateInfo> {
const { data, keyToIndex, cacheHitIndices, sentinelEmptyIndices } = this;
const index = keyToIndex.get(key);
if (index == null) {
return AbsentReason.NotSeen;
}
Expand All @@ -72,6 +88,8 @@ class ComparisonDataset {
}
}
return {
name: data.names[index],
raHash: data.raHashes[index],
evaluationCount: data.evaluationCounts[index],
iterationCount: data.iterationCounts[index],
timeCost: data.timeCosts[index],
Expand Down Expand Up @@ -336,6 +354,7 @@ function HighLevelStats(props: HighLevelStatsProps) {
}

interface Row {
key: string;
name: string;
before: Optional<PredicateInfo>;
after: Optional<PredicateInfo>;
Expand Down Expand Up @@ -480,19 +499,16 @@ function ComparePerformanceWithData(props: {

const [isPerEvaluation, setPerEvaluation] = useState(false);

const nameSet = useMemo(
() => union(from.data.names, to.data.names),
[from, to],
);
const keySet = useMemo(() => union(from.keys(), to.keys()), [from, to]);

const hasCacheHitMismatch = useRef(false);

const rows: Row[] = useMemo(() => {
hasCacheHitMismatch.current = false;
return Array.from(nameSet)
.map((name) => {
const before = from.getTupleCountInfo(name);
const after = to.getTupleCountInfo(name);
return Array.from(keySet)
.map((key) => {
const before = from.getTupleCountInfo(key);
const after = to.getTupleCountInfo(key);
const beforeValue = metricGetOptional(metric, before, isPerEvaluation);
const afterValue = metricGetOptional(metric, after, isPerEvaluation);
if (beforeValue === afterValue) {
Expand All @@ -510,11 +526,16 @@ function ComparePerformanceWithData(props: {
const diff =
(isPresent(afterValue) ? afterValue : 0) -
(isPresent(beforeValue) ? beforeValue : 0);
return { name, before, after, diff } satisfies Row;
const name = isPresent(before)
? before.name
: isPresent(after)
? after.name
: key;
return { key, name, before, after, diff } satisfies Row;
})
.filter((x) => !!x)
.sort(getSortOrder(sortOrder));
}, [nameSet, from, to, metric, hideCacheHits, sortOrder, isPerEvaluation]);
}, [keySet, from, to, metric, hideCacheHits, sortOrder, isPerEvaluation]);

const { totalBefore, totalAfter, totalDiff } = useMemo(() => {
let totalBefore = 0;
Expand Down Expand Up @@ -860,3 +881,14 @@ function collatePipelines(
function samePipeline(a: string[], b: string[]) {
return a.length === b.length && a.every((x, i) => x === b[i]);
}

function getPipelineSummaryHash(pipelines: Record<string, PipelineSummary>) {
// Note: we can't import "crypto" here because it is not available in the browser,
// so we just concatenate the hashes of the individual pipelines.
Comment on lines +977 to +978
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How long will this be? Can we possibly have 100s of keys? It might be safer to do some checks to ensure that the result is never larger than a fixed amount. And if it is, then you can sample the first n chars in each key to keep it under the limit, retain a reasonable amount of stochasticity, and ensure the result easy to reproduce given the same input.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Back when the "standard order" existed there could be multiple pipelines, but nowadays there are 2 for recursive predicates and 1 for all other predicates. So it's really small.

const keys = Object.keys(pipelines).sort();
let result = "";
for (const key of keys) {
result += `${pipelines[key].hash};`;
}
return result;
}