Skip to content

Commit f427fb3

Browse files
anonrigGrok
andcommitted
src: reuse zstd contexts in compile cache
Creating and freeing a zstd context for every cache file costs more than the (de)compression itself for small caches. Lazily create one decompression context on the handler and reuse it across reads, and share one compression context across all entries in Persist(). Co-authored-by: Grok <grok@x.ai> Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
1 parent fb86fb9 commit f427fb3

2 files changed

Lines changed: 45 additions & 17 deletions

File tree

src/compile_cache.cc

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,8 @@ void CompileCacheHandler::ReadCacheFile(CompileCacheEntry* entry) {
202202
// and zstd-compressed when cache_size < raw_size (see
203203
// CompileCacheHandler::Persist()). Anything else is invalid.
204204
if (cache_size > raw_size) {
205-
Debug("invalid cache size %d > uncompressed size %d\n",
206-
cache_size,
207-
raw_size);
205+
Debug(
206+
"invalid cache size %d > uncompressed size %d\n", cache_size, raw_size);
208207
return;
209208
}
210209

@@ -260,13 +259,19 @@ void CompileCacheHandler::ReadCacheFile(CompileCacheEntry* entry) {
260259
content_size);
261260
return;
262261
}
262+
// Lazily create the decompression context on first use and reuse it
263+
// for subsequent reads - recreating its workspace for every file
264+
// costs more than the decompression itself for small caches.
265+
if (zstd_dctx_ == nullptr && (zstd_dctx_ = ZSTD_createDCtx()) == nullptr) {
266+
Debug("failed to create zstd context\n");
267+
return;
268+
}
263269
// Decompress directly into the buffer handed to V8.
264270
std::unique_ptr<uint8_t[]> raw_data(new uint8_t[raw_size]);
265-
size_t decompressed_size =
266-
ZSTD_decompress(raw_data.get(), raw_size, disk_data.get(), cache_size);
271+
size_t decompressed_size = ZSTD_decompressDCtx(
272+
zstd_dctx_, raw_data.get(), raw_size, disk_data.get(), cache_size);
267273
if (ZSTD_isError(decompressed_size)) {
268-
Debug("decompression failed: %s\n",
269-
ZSTD_getErrorName(decompressed_size));
274+
Debug("decompression failed: %s\n", ZSTD_getErrorName(decompressed_size));
270275
return;
271276
}
272277
if (decompressed_size != raw_size) {
@@ -276,9 +281,7 @@ void CompileCacheHandler::ReadCacheFile(CompileCacheEntry* entry) {
276281
return;
277282
}
278283
entry->cache.reset(new ScriptCompiler::CachedData(
279-
raw_data.release(),
280-
raw_size,
281-
ScriptCompiler::CachedData::BufferOwned));
284+
raw_data.release(), raw_size, ScriptCompiler::CachedData::BufferOwned));
282285
}
283286
Debug(" success, size=%d\n", raw_size);
284287
}
@@ -459,6 +462,16 @@ void CompileCacheHandler::Persist() {
459462
// finished. In that case, the off-thread writes should finish long
460463
// before any attempt of flushing is made so the method would then only
461464
// incur a negligible overhead from thread synchronization.
465+
466+
// The compression context is created lazily when there is anything to
467+
// compress and reused for all the entries in this invocation.
468+
ZSTD_CCtx* cctx = nullptr;
469+
auto cleanup_cctx = OnScopeLeave([&cctx]() {
470+
if (cctx != nullptr) {
471+
ZSTD_freeCCtx(cctx);
472+
}
473+
});
474+
462475
for (auto& pair : compiler_cache_store_) {
463476
auto* entry = pair.second.get();
464477
const char* type_name = entry->type_name();
@@ -495,15 +508,18 @@ void CompileCacheHandler::Persist() {
495508
// shutdown and should add as little overhead as possible. If the data
496509
// is not compressible, store it uncompressed, which is indicated by
497510
// the cache size being equal to the uncompressed size in the headers.
498-
size_t compressed_bound = ZSTD_compressBound(raw_size);
499-
std::unique_ptr<uint8_t[]> compressed(new uint8_t[compressed_bound]);
500-
size_t compressed_size = ZSTD_compress(
501-
compressed.get(), compressed_bound, raw_ptr, raw_size, 1);
502511
char* cache_ptr = raw_ptr;
503512
uint32_t cache_size = raw_size;
504-
if (!ZSTD_isError(compressed_size) && compressed_size < raw_size) {
505-
cache_ptr = reinterpret_cast<char*>(compressed.get());
506-
cache_size = static_cast<uint32_t>(compressed_size);
513+
std::unique_ptr<uint8_t[]> compressed;
514+
if (cctx != nullptr || (cctx = ZSTD_createCCtx()) != nullptr) {
515+
size_t compressed_bound = ZSTD_compressBound(raw_size);
516+
compressed.reset(new uint8_t[compressed_bound]);
517+
size_t compressed_size = ZSTD_compressCCtx(
518+
cctx, compressed.get(), compressed_bound, raw_ptr, raw_size, 1);
519+
if (!ZSTD_isError(compressed_size) && compressed_size < raw_size) {
520+
cache_ptr = reinterpret_cast<char*>(compressed.get());
521+
cache_size = static_cast<uint32_t>(compressed_size);
522+
}
507523
}
508524
Debug("[compile cache] compressed cache for %s %s: %d -> %d bytes\n",
509525
type_name,
@@ -621,6 +637,12 @@ CompileCacheHandler::CompileCacheHandler(Environment* env)
621637
is_debug_(
622638
env->enabled_debug_list()->enabled(DebugCategory::COMPILE_CACHE)) {}
623639

640+
CompileCacheHandler::~CompileCacheHandler() {
641+
if (zstd_dctx_ != nullptr) {
642+
ZSTD_freeDCtx(zstd_dctx_);
643+
}
644+
}
645+
624646
// Directory structure:
625647
// - Compile cache directory (from NODE_COMPILE_CACHE)
626648
// - $NODE_VERSION-$ARCH-$CACHE_DATA_VERSION_TAG-$UID

src/compile_cache.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include <unordered_map>
1111
#include "v8.h"
1212

13+
struct ZSTD_DCtx_s;
14+
1315
namespace node {
1416
class Environment;
1517

@@ -67,6 +69,7 @@ enum class EnableOption : uint8_t { DEFAULT, PORTABLE };
6769
class CompileCacheHandler {
6870
public:
6971
explicit CompileCacheHandler(Environment* env);
72+
~CompileCacheHandler();
7073
CompileCacheEnableResult Enable(Environment* env,
7174
const std::string& dir,
7275
EnableOption option = EnableOption::DEFAULT);
@@ -113,6 +116,9 @@ class CompileCacheHandler {
113116
EnableOption portable_ = EnableOption::DEFAULT;
114117
std::unordered_map<uint32_t, std::unique_ptr<CompileCacheEntry>>
115118
compiler_cache_store_;
119+
// Lazily created zstd decompression context, reused across cache reads
120+
// to avoid recreating its workspace for every file.
121+
ZSTD_DCtx_s* zstd_dctx_ = nullptr;
116122
};
117123
} // namespace node
118124

0 commit comments

Comments
 (0)