refactor: mem cache puts optimization
This commit is contained in:
committed by
Piotr Pędziwiatr
parent
fd7a63db6d
commit
1f35fe6ce5
@@ -40,7 +40,7 @@ describe.each(chunked)('.suite %#', (contracts: string[]) => {
|
|||||||
const result2 = await SmartWeaveNodeFactory.memCached(arweave, 5)
|
const result2 = await SmartWeaveNodeFactory.memCached(arweave, 5)
|
||||||
.contract(contractTxId)
|
.contract(contractTxId)
|
||||||
.setEvaluationOptions({
|
.setEvaluationOptions({
|
||||||
fcpOptimization: true
|
updateCacheForEachInteraction: false
|
||||||
})
|
})
|
||||||
.readState();
|
.readState();
|
||||||
const result2String = JSON.stringify(result2.state).trim();
|
const result2String = JSON.stringify(result2.state).trim();
|
||||||
|
|||||||
39
src/cache/impl/MemBlockHeightCache.ts
vendored
39
src/cache/impl/MemBlockHeightCache.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
import { BlockHeightCacheResult, BlockHeightKey, BlockHeightSwCache } from '@smartweave/cache';
|
import { BlockHeightCacheResult, BlockHeightKey, BlockHeightSwCache } from '@smartweave/cache';
|
||||||
import { asc, deepCopy, desc } from '@smartweave/utils';
|
import { ascS, deepCopy, descS } from '@smartweave/utils';
|
||||||
import { LoggerFactory } from '@smartweave/logging';
|
import { LoggerFactory } from '@smartweave/logging';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8,7 +8,9 @@ import { LoggerFactory } from '@smartweave/logging';
|
|||||||
export class MemBlockHeightSwCache<V = any> implements BlockHeightSwCache<V> {
|
export class MemBlockHeightSwCache<V = any> implements BlockHeightSwCache<V> {
|
||||||
private readonly logger = LoggerFactory.INST.create('MemBlockHeightSwCache');
|
private readonly logger = LoggerFactory.INST.create('MemBlockHeightSwCache');
|
||||||
|
|
||||||
protected storage: { [key: string]: Map<number, V> } = {};
|
// not using map here, as setting values in map seems to be slower
|
||||||
|
// then setting value for simple object - see tools/map-benchmark.ts
|
||||||
|
protected storage: { [contractId: string]: { [key: string]: V } } = {};
|
||||||
|
|
||||||
constructor(private maxStoredBlockHeights: number = Number.MAX_SAFE_INTEGER) {}
|
constructor(private maxStoredBlockHeights: number = Number.MAX_SAFE_INTEGER) {}
|
||||||
|
|
||||||
@@ -21,15 +23,15 @@ export class MemBlockHeightSwCache<V = any> implements BlockHeightSwCache<V> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cached: Map<number, V> = this.storage[key];
|
const cached = this.storage[key];
|
||||||
|
|
||||||
// sort keys (ie. block heights) in asc order and get
|
// sort keys (ie. block heights) in asc order and get
|
||||||
// the last element (ie. highest cached block height).
|
// the last element (ie. highest cached block height).
|
||||||
const highestBlockHeight = [...cached.keys()].sort(asc).pop();
|
const highestBlockHeight = [...Object.keys(cached)].sort(ascS).pop();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cachedHeight: highestBlockHeight,
|
cachedHeight: +highestBlockHeight,
|
||||||
cachedValue: deepCopy(cached.get(highestBlockHeight))
|
cachedValue: deepCopy(cached[highestBlockHeight])
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,32 +40,33 @@ export class MemBlockHeightSwCache<V = any> implements BlockHeightSwCache<V> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cached: Map<number, V> = this.storage[key];
|
const cached = this.storage[key];
|
||||||
|
|
||||||
// find first element in and desc-sorted keys array that is not higher than requested block height
|
// find first element in and desc-sorted keys array that is not higher than requested block height
|
||||||
const highestBlockHeight = [...cached.keys()].sort(desc).find((cachedBlockHeight) => {
|
const highestBlockHeight = [...Object.keys(cached)].sort(descS).find((cachedBlockHeight) => {
|
||||||
return cachedBlockHeight <= blockHeight;
|
return +cachedBlockHeight <= blockHeight;
|
||||||
});
|
});
|
||||||
|
|
||||||
return highestBlockHeight === undefined
|
return highestBlockHeight === undefined
|
||||||
? null
|
? null
|
||||||
: {
|
: {
|
||||||
cachedHeight: highestBlockHeight,
|
cachedHeight: +highestBlockHeight,
|
||||||
cachedValue: deepCopy(cached.get(highestBlockHeight))
|
cachedValue: deepCopy(cached[highestBlockHeight])
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async put({ cacheKey, blockHeight }: BlockHeightKey, value: V): Promise<void> {
|
async put({ cacheKey, blockHeight }: BlockHeightKey, value: V): Promise<void> {
|
||||||
if (!(await this.contains(cacheKey))) {
|
if (!(await this.contains(cacheKey))) {
|
||||||
this.storage[cacheKey] = new Map();
|
this.storage[cacheKey] = {};
|
||||||
}
|
}
|
||||||
const cached = this.storage[cacheKey];
|
const cached = this.storage[cacheKey];
|
||||||
if (cached.size == this.maxStoredBlockHeights) {
|
const cachedKeys = Object.keys(cached);
|
||||||
const toRemove = [...cached.keys()].sort(asc).shift();
|
if (cachedKeys.length == this.maxStoredBlockHeights) {
|
||||||
cached.delete(toRemove);
|
const toRemove = [...cachedKeys].sort(ascS).shift();
|
||||||
|
delete cached[toRemove];
|
||||||
}
|
}
|
||||||
|
|
||||||
cached.set(blockHeight, deepCopy(value));
|
cached['' + blockHeight] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async contains(key: string): Promise<boolean> {
|
async contains(key: string): Promise<boolean> {
|
||||||
@@ -75,13 +78,13 @@ export class MemBlockHeightSwCache<V = any> implements BlockHeightSwCache<V> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.storage[key].has(blockHeight)) {
|
if (!this.storage[key].hasOwnProperty('' + blockHeight)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cachedHeight: blockHeight,
|
cachedHeight: blockHeight,
|
||||||
cachedValue: deepCopy(this.storage[key].get(blockHeight))
|
cachedValue: deepCopy(this.storage[key]['' + blockHeight])
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
]);
|
]);
|
||||||
this.logger.debug('contract and interactions load', benchmark.elapsed());
|
this.logger.debug('contract and interactions load', benchmark.elapsed());
|
||||||
sortedInteractions = await interactionsSorter.sort(interactions);
|
sortedInteractions = await interactionsSorter.sort(interactions);
|
||||||
this.logger.debug('Sorted interactions', sortedInteractions);
|
this.logger.trace('Sorted interactions', sortedInteractions);
|
||||||
handler = (await executorFactory.create(contractDefinition)) as HandlerApi<State>;
|
handler = (await executorFactory.create(contractDefinition)) as HandlerApi<State>;
|
||||||
} else {
|
} else {
|
||||||
this.logger.debug('State fully cached, not loading interactions.');
|
this.logger.debug('State fully cached, not loading interactions.');
|
||||||
|
|||||||
@@ -61,7 +61,10 @@ export interface StateEvaluator {
|
|||||||
blockHeight: number
|
blockHeight: number
|
||||||
): Promise<BlockHeightCacheResult<EvalStateResult<State>> | null>;
|
): Promise<BlockHeightCacheResult<EvalStateResult<State>> | null>;
|
||||||
|
|
||||||
transactionState<State>(transaction: GQLNodeInterface, contractTxId: string): Promise<EvalStateResult<State> | undefined>;
|
transactionState<State>(
|
||||||
|
transaction: GQLNodeInterface,
|
||||||
|
contractTxId: string
|
||||||
|
): Promise<EvalStateResult<State> | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EvalStateResult<State> {
|
export class EvalStateResult<State> {
|
||||||
@@ -69,7 +72,8 @@ export class EvalStateResult<State> {
|
|||||||
readonly state: State,
|
readonly state: State,
|
||||||
readonly validity: Record<string, boolean>,
|
readonly validity: Record<string, boolean>,
|
||||||
readonly transactionId?: string,
|
readonly transactionId?: string,
|
||||||
readonly blockId?: string) {}
|
readonly blockId?: string
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DefaultEvaluationOptions implements EvaluationOptions {
|
export class DefaultEvaluationOptions implements EvaluationOptions {
|
||||||
|
|||||||
@@ -148,32 +148,6 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if (stateCache.cachedValue.length == 1) {
|
|
||||||
this.cLogger.debug('CacheValue size 1', stateCache.cachedValue.values().next().value);
|
|
||||||
return new BlockHeightCacheResult<EvalStateResult<State>>(
|
|
||||||
stateCache.cachedHeight,
|
|
||||||
stateCache.cachedValue.values().next().value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sorter = new LexicographicalInteractionsSorter(this.arweave);
|
|
||||||
|
|
||||||
this.cLogger.debug('State cache', JSON.stringify(stateCache.cachedValue, mapReplacer));
|
|
||||||
const toSort = await Promise.all(
|
|
||||||
[...stateCache.cachedValue.values()].map(async (k) => {
|
|
||||||
return {
|
|
||||||
transactionId: k.transactionId,
|
|
||||||
sortKey: await sorter.createSortKey(k.blockId, k.transactionId, blockHeight)
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const sorted = toSort.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
||||||
this.cLogger.debug('sorted:', sorted);
|
|
||||||
|
|
||||||
const lastKey = sorted.pop();
|
|
||||||
|
|
||||||
this.cLogger.debug('Last key: ', lastKey);*/
|
|
||||||
|
|
||||||
return new BlockHeightCacheResult<EvalStateResult<State>>(
|
return new BlockHeightCacheResult<EvalStateResult<State>>(
|
||||||
stateCache.cachedHeight,
|
stateCache.cachedHeight,
|
||||||
[...stateCache.cachedValue].pop()
|
[...stateCache.cachedValue].pop()
|
||||||
@@ -198,7 +172,8 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
|
|||||||
executionContext: ExecutionContext<State>,
|
executionContext: ExecutionContext<State>,
|
||||||
state: EvalStateResult<State>
|
state: EvalStateResult<State>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.putInCache(executionContext.contractDefinition.txId, transaction, state);
|
//FIXME: https://github.com/redstone-finance/redstone-smartcontracts/issues/53
|
||||||
|
//await this.putInCache(executionContext.contractDefinition.txId, transaction, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
async transactionState<State>(
|
async transactionState<State>(
|
||||||
@@ -228,10 +203,7 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
|
|||||||
}
|
}
|
||||||
const transactionId = transaction.id;
|
const transactionId = transaction.id;
|
||||||
const blockHeight = transaction.block.height;
|
const blockHeight = transaction.block.height;
|
||||||
this.cLogger.debug('putInCache:', {
|
|
||||||
state: state.state,
|
|
||||||
transactionId
|
|
||||||
});
|
|
||||||
const stateToCache = new EvalStateResult(state.state, state.validity, transactionId, transaction.block.id);
|
const stateToCache = new EvalStateResult(state.state, state.validity, transactionId, transaction.block.id);
|
||||||
const stateCache = await this.cache.get(contractTxId, blockHeight);
|
const stateCache = await this.cache.get(contractTxId, blockHeight);
|
||||||
if (stateCache != null) {
|
if (stateCache != null) {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export abstract class DefaultStateEvaluator implements StateEvaluator {
|
|||||||
`Evaluating state for ${contractDefinition.txId} [${missingInteractions.length} non-cached of ${sortedInteractions.length} all]`
|
`Evaluating state for ${contractDefinition.txId} [${missingInteractions.length} non-cached of ${sortedInteractions.length} all]`
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.debug('Base state:', baseState.state);
|
this.logger.trace('Base state:', baseState.state);
|
||||||
|
|
||||||
let lastEvaluatedInteraction = null;
|
let lastEvaluatedInteraction = null;
|
||||||
let errorMessage = null;
|
let errorMessage = null;
|
||||||
@@ -215,7 +215,6 @@ export abstract class DefaultStateEvaluator implements StateEvaluator {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await this.onStateUpdate<State>(interactionTx, executionContext, new EvalStateResult(currentState, validity));
|
await this.onStateUpdate<State>(interactionTx, executionContext, new EvalStateResult(currentState, validity));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// I'm really NOT a fan of this "modify" feature, but I don't have idea how to better
|
// I'm really NOT a fan of this "modify" feature, but I don't have idea how to better
|
||||||
|
|||||||
@@ -29,8 +29,12 @@ export const mapReviver = (key: unknown, value: any) => {
|
|||||||
|
|
||||||
export const asc = (a: number, b: number): number => a - b;
|
export const asc = (a: number, b: number): number => a - b;
|
||||||
|
|
||||||
|
export const ascS = (a: string, b: string): number => +a - +b;
|
||||||
|
|
||||||
export const desc = (a: number, b: number): number => b - a;
|
export const desc = (a: number, b: number): number => b - a;
|
||||||
|
|
||||||
|
export const descS = (a: string, b: string): number => +b - +a;
|
||||||
|
|
||||||
export function timeout(s: number): { timeoutId: number; timeoutPromise: Promise<any> } {
|
export function timeout(s: number): { timeoutId: number; timeoutPromise: Promise<any> } {
|
||||||
let timeoutId = null;
|
let timeoutId = null;
|
||||||
const timeoutPromise = new Promise((resolve, reject) => {
|
const timeoutPromise = new Promise((resolve, reject) => {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
16402
tools/data/transactions-live.json
Normal file
16402
tools/data/transactions-live.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { SmartWeaveWebFactory } from '../src/core/web/SmartWeaveWebFactory';
|
import { SmartWeaveWebFactory } from '../src/core/web/SmartWeaveWebFactory';
|
||||||
import { FromFileInteractionsLoader } from './FromFileInteractionsLoader';
|
import { FromFileInteractionsLoader } from './FromFileInteractionsLoader';
|
||||||
import { readContract } from 'smartweave';
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
LoggerFactory.use(new TsLogFactory());
|
LoggerFactory.use(new TsLogFactory());
|
||||||
@@ -20,13 +19,18 @@ async function main() {
|
|||||||
logging: false // Enable network request logging
|
logging: false // Enable network request logging
|
||||||
});
|
});
|
||||||
|
|
||||||
const contractTxId = 'Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY';
|
const contractTxId = 'w27141UQGgrCFhkiw9tL7A0-qWMQjbapU3mq2TfI4Cg';
|
||||||
|
|
||||||
const interactionsLoader = new FromFileInteractionsLoader(path.join(__dirname, 'data', 'interactions.json'));
|
const interactionsLoader = new FromFileInteractionsLoader(path.join(__dirname, 'data', 'interactions.json'));
|
||||||
|
|
||||||
const smartweave = SmartWeaveWebFactory.memCachedBased(arweave).setInteractionsLoader(interactionsLoader).build();
|
const smartweave = SmartWeaveWebFactory.memCachedBased(arweave, 1)
|
||||||
|
.build();
|
||||||
|
|
||||||
const lootContract = smartweave.contract(contractTxId);
|
const lootContract = smartweave
|
||||||
|
.contract(contractTxId)
|
||||||
|
.setEvaluationOptions({
|
||||||
|
updateCacheForEachInteraction: false
|
||||||
|
});
|
||||||
|
|
||||||
const { state, validity } = await lootContract.readState();
|
const { state, validity } = await lootContract.readState();
|
||||||
|
|
||||||
@@ -35,10 +39,6 @@ async function main() {
|
|||||||
|
|
||||||
//fs.writeFileSync(path.join(__dirname, 'data', 'validity_old.json'), JSON.stringify(result.validity));
|
//fs.writeFileSync(path.join(__dirname, 'data', 'validity_old.json'), JSON.stringify(result.validity));
|
||||||
fs.writeFileSync(path.join(__dirname, 'data', 'state.json'), JSON.stringify(state));
|
fs.writeFileSync(path.join(__dirname, 'data', 'state.json'), JSON.stringify(state));
|
||||||
|
|
||||||
|
|
||||||
console.log('second read');
|
|
||||||
await lootContract.readState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((e) => console.error(e));
|
main().catch((e) => console.error(e));
|
||||||
|
|||||||
27
tools/map-benchmark.ts
Normal file
27
tools/map-benchmark.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
import Arweave from 'arweave';
|
||||||
|
import { LoggerFactory } from '../src';
|
||||||
|
import { TsLogFactory } from '../src/logging/node/TsLogFactory';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
LoggerFactory.use(new TsLogFactory());
|
||||||
|
|
||||||
|
LoggerFactory.INST.logLevel('debug');
|
||||||
|
|
||||||
|
console.time('benchmark_map');
|
||||||
|
const map = new Map();
|
||||||
|
for (let i = 0; i < 10_000_000; i++) {
|
||||||
|
map.set(i, 'duh');
|
||||||
|
}
|
||||||
|
console.timeEnd('benchmark_map');
|
||||||
|
|
||||||
|
console.time('benchmark_object');
|
||||||
|
const o = {};
|
||||||
|
for (let i = 0; i < 10_000_000; i++) {
|
||||||
|
o[i] = 'duh';
|
||||||
|
}
|
||||||
|
console.timeEnd('benchmark_object');
|
||||||
|
console.log(Object.keys(o).length);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => console.error(e));
|
||||||
Reference in New Issue
Block a user