From 7dc78e7a1a9b0a1ea5aaf722709e4c047bbc8ee2 Mon Sep 17 00:00:00 2001 From: Vsevolod Pukhov Date: Mon, 9 Jun 2025 20:59:42 +0300 Subject: [PATCH 1/2] tuning Signed-off-by: Vsevolod Pukhov --- ets2panda/checker/types/ets/etsObjectType.cpp | 56 +++++--- ets2panda/checker/types/ets/etsObjectType.h | 2 +- ets2panda/checker/types/typeRelation.cpp | 52 ++++++++ ets2panda/compiler/core/compilerImpl.cpp | 58 +++++++- ets2panda/ir/astNode.cpp | 126 +----------------- ets2panda/ir/astNode.h | 116 +++++++++++++--- ets2panda/util/generateBin.cpp | 44 +++++- 7 files changed, 289 insertions(+), 165 deletions(-) diff --git a/ets2panda/checker/types/ets/etsObjectType.cpp b/ets2panda/checker/types/ets/etsObjectType.cpp index c86dbcd510..048b6a0650 100644 --- a/ets2panda/checker/types/ets/etsObjectType.cpp +++ b/ets2panda/checker/types/ets/etsObjectType.cpp @@ -67,35 +67,46 @@ varbinder::LocalVariable *ETSObjectType::SearchFieldsDecls(util::StringView name varbinder::LocalVariable *ETSObjectType::GetProperty(util::StringView name, PropertySearchFlags flags) const { - varbinder::LocalVariable *res = SearchFieldsDecls(name, flags); - if (res == nullptr && (flags & PropertySearchFlags::SEARCH_METHOD) != 0) { + if (auto res = SearchFieldsDecls(name, flags); res != nullptr) { + return res; + } + + if ((flags & PropertySearchFlags::SEARCH_METHOD) != 0) { if ((flags & PropertySearchFlags::DISALLOW_SYNTHETIC_METHOD_CREATION) != 0) { if ((flags & PropertySearchFlags::SEARCH_INSTANCE_METHOD) != 0) { - res = GetOwnProperty(name); + auto res = GetOwnProperty(name); + if (res != nullptr) { + return res; + } } - - if (res == nullptr && ((flags & PropertySearchFlags::SEARCH_STATIC_METHOD) != 0)) { - res = GetOwnProperty(name); + if ((flags & PropertySearchFlags::SEARCH_STATIC_METHOD) != 0) { + auto res = GetOwnProperty(name); + if (res != nullptr) { + return res; + } } } else { - res = CreateSyntheticVarFromEverySignature(name, flags); + if (auto res = CreateSyntheticVarFromEverySignature(name, flags)) { + return res; + } } } - if (res == nullptr && (flags & PropertySearchFlags::SEARCH_IN_INTERFACES) != 0) { + if (((flags & PropertySearchFlags::SEARCH_INSTANCE) != 0 || (flags & PropertySearchFlags::SEARCH_STATIC) == 0) && + (flags & PropertySearchFlags::SEARCH_IN_INTERFACES) != 0) { for (auto *interface : interfaces_) { - res = interface->GetProperty(name, flags); + auto res = interface->GetProperty(name, flags); if (res != nullptr) { return res; } } } - if (res == nullptr && superType_ != nullptr && ((flags & PropertySearchFlags::SEARCH_IN_BASE) != 0)) { - res = superType_->GetProperty(name, flags); + if ((flags & PropertySearchFlags::SEARCH_IN_BASE) != 0 && superType_ != nullptr) { + return superType_->GetProperty(name, flags); } - return res; + return nullptr; } bool ETSObjectType::IsPropertyInherited(const varbinder::Variable *var) @@ -266,13 +277,26 @@ static void CollectSignaturesForSyntheticType(ETSObjectType const *owner, } } + if ((flags & PropertySearchFlags::SEARCH_INSTANCE_METHOD) == 0) { + return; + } + if (owner->SuperType() != nullptr && ((flags & PropertySearchFlags::SEARCH_IN_BASE) != 0)) { CollectSignaturesForSyntheticType(owner->SuperType(), signatureSet, name, flags); } - if ((flags & PropertySearchFlags::SEARCH_IN_INTERFACES) != 0) { - for (auto *interface : owner->Interfaces()) { - CollectSignaturesForSyntheticType(interface, signatureSet, name, flags); + if ((flags & PropertySearchFlags::SEARCH_IN_INTERFACES) == 0 || owner->IsPartial()) { // NOTE: issue 24548 + return; + } + + for (auto *interface : owner->Interfaces()) { + if (interface == nullptr) { + continue; + } + if (auto *found = interface->GetProperty(name, flags | PropertySearchFlags::DISALLOW_SYNTHETIC_METHOD_CREATION); + found != nullptr && !found->TsType()->IsTypeError()) { + ES2PANDA_ASSERT(found->TsType()->IsETSFunctionType()); + AddSignatureToSignatureSet(signatureSet, found, owner->GetRelation(), flags); } } } @@ -1268,7 +1292,7 @@ ETSObjectType *ETSObjectType::SubstituteArguments(TypeRelation *relation, ArenaV return Substitute(relation, substitution); } -ETSChecker *ETSObjectType::GetETSChecker() +ETSChecker *ETSObjectType::GetETSChecker() const { return relation_->GetChecker()->AsETSChecker(); } diff --git a/ets2panda/checker/types/ets/etsObjectType.h b/ets2panda/checker/types/ets/etsObjectType.h index 4c8538f082..9cbccee016 100644 --- a/ets2panda/checker/types/ets/etsObjectType.h +++ b/ets2panda/checker/types/ets/etsObjectType.h @@ -74,7 +74,7 @@ public: } } - ETSChecker *GetETSChecker(); + ETSChecker *GetETSChecker() const; void SetSuperType(ETSObjectType *super) { diff --git a/ets2panda/checker/types/typeRelation.cpp b/ets2panda/checker/types/typeRelation.cpp index 80a8ad001a..40a798ff92 100644 --- a/ets2panda/checker/types/typeRelation.cpp +++ b/ets2panda/checker/types/typeRelation.cpp @@ -254,9 +254,46 @@ bool TypeRelation::IsLegalBoxedPrimitiveConversion(Type *target, Type *source) return res; } +static struct { + std::array byflag {}; + size_t sameptr {}; + size_t diffkind {}; + size_t cached {}; + size_t missed {}; + size_t identical {}; + size_t missfalse {}; + + void tk(TypeFlag tf) + { + auto idx = Ffs(helpers::ToUnderlying(tf)) - 1; + (byflag[idx])++; + } + + void dumpstats() + { + std::cerr << "STYPE STATS\n"; + std::cerr << "sameptr " << sameptr << "\n"; + std::cerr << "diffkind " << diffkind << "\n"; + std::cerr << "cached " << cached << "\n"; + std::cerr << "missed " << missed << "\n"; + std::cerr << "identical " << identical << "\n"; + std::cerr << "missfalse " << missfalse << "\n"; + + for (size_t s = 0; s < byflag.size(); ++s) { + std::cerr << "id: " << s << " " << byflag[s] << "\n"; + } + } +} g_stats; + +__attribute__((destructor)) void dumpstats() +{ + // g_stats.dumpstats(); +} + bool TypeRelation::IsSupertypeOf(Type *super, Type *sub) { if (super == sub) { + g_stats.sameptr++; return Result(true); } @@ -264,9 +301,19 @@ bool TypeRelation::IsSupertypeOf(Type *super, Type *sub) return false; } + if (super->IsETSPrimitiveType() != sub->IsETSPrimitiveType()) { + g_stats.diffkind++; + return false; + } + + g_stats.tk(ETSChecker::TypeKind(super)); + g_stats.tk(ETSChecker::TypeKind(sub)); + result_ = CacheLookup(super, sub, checker_->SupertypeResults(), RelationType::SUPERTYPE); if (result_ == RelationResult::CACHE_MISS) { + g_stats.missed++; if (IsIdenticalTo(super, sub)) { + g_stats.identical++; return true; } @@ -274,10 +321,15 @@ bool TypeRelation::IsSupertypeOf(Type *super, Type *sub) if (super->IsSupertypeOf(this, sub), !IsTrue()) { sub->IsSubtypeOf(this, super); } + if (result_ == RelationResult::FALSE) { + g_stats.missfalse++; + } if (flags_ == TypeRelationFlag::NONE) { checker_->SupertypeResults().cached.insert({{super->Id(), sub->Id()}, {result_, RelationType::SUPERTYPE}}); } + } else { + g_stats.cached++; } return result_ == RelationResult::TRUE; diff --git a/ets2panda/compiler/core/compilerImpl.cpp b/ets2panda/compiler/core/compilerImpl.cpp index 7086b2707d..6f72a53383 100644 --- a/ets2panda/compiler/core/compilerImpl.cpp +++ b/ets2panda/compiler/core/compilerImpl.cpp @@ -52,6 +52,8 @@ #include "varbinder/TSBinder.h" #include "varbinder/ETSBinder.h" +#include "sys/resource.h" + namespace ark::es2panda::compiler { void CompilerImpl::HandleContextLiterals(public_lib::Context *context) @@ -179,11 +181,44 @@ static bool CheckIfPhaseToSkip(util::Options &options, const std::string &name) (options.IsGenerateDeclEnableIsolated() && name == compiler::InsertOptionalParametersAnnotation::NAME); } +struct ScopedTimer { + ScopedTimer(std::string name_) + { + name = name_; + start = std::chrono::system_clock::now(); + srss = getrss(); + } + + static long getrss() + { + struct rusage ru; + if (getrusage(RUSAGE_SELF, &ru) != 0) { + abort(); + } + return ru.ru_maxrss; + } + + ~ScopedTimer() + { + auto end = std::chrono::system_clock::now(); + auto timeMs = std::chrono::duration_cast(end - start).count(); + if (timeMs == 0) { + return; + } + [[maybe_unused]] auto rss = (getrss() - srss); + // printf("@%-40s: %5zums %6ldkb\n", name.c_str(), timeMs, rss); + } + + std::string name; + std::chrono::time_point start; + long srss; +}; + // CC-OFFNXT(huge_method[C++], G.FUN.01-CPP, G.FUD.05) solid logic static bool RunVerifierAndPhases(public_lib::Context &context, parser::Program &program) { auto &options = const_cast(*context.config->options); - const auto verifierEachPhase = options.IsAstVerifierEachPhase(); + // const auto verifierEachPhase = options.IsAstVerifierEachPhase(); ast_verifier::ASTVerifier verifier(context, program); checker::IsolatedDeclgenChecker isolatedDeclgenChecker(*context.diagnosticEngine, program); @@ -198,7 +233,12 @@ static bool RunVerifierAndPhases(public_lib::Context &context, parser::Program & afterCheckerPhase = true; } - if (CheckIfPhaseToSkip(options, name)) { + ScopedTimer timer(name); + + bool skipPhase = options.GetSkipPhases().count(name) > 0 || + (options.IsGenerateDeclEnableIsolated() && + phase->Name() == compiler::InsertOptionalParametersAnnotation::NAME); + if (skipPhase) { continue; } @@ -219,9 +259,9 @@ static bool RunVerifierAndPhases(public_lib::Context &context, parser::Program & verifier.IntroduceNewInvariants(phase->Name()); } - if (verifierEachPhase || options.HasVerifierPhase(phase->Name())) { - verifier.Verify(phase->Name()); - } + // if (verifierEachPhase || options.HasVerifierPhase(phase->Name())) { + // verifier.Verify(phase->Name()); + // } // Stop lowerings processing after Checker phase if any error happened. if (afterCheckerPhase && context.diagnosticEngine->IsAnyError()) { @@ -413,13 +453,17 @@ static pandasm::Program *EmitProgram(CompilerImpl *compilerImpl, public_lib::Con static bool ExecuteParsingAndCompiling(const CompilationUnit &unit, public_lib::Context *context) { + ScopedTimer timer("Parse and compile overall"); parser::Program *program = context->parserProgram; if (unit.ext == ScriptExtension::ETS && context->compilingState == public_lib::CompilingState::MULTI_COMPILING_FOLLOW) { AddExternalPrograms(context, unit, program); } - context->parser->ParseScript(unit.input, unit.options.GetCompilationMode() == CompilationMode::GEN_STD_LIB); + { + ScopedTimer timeremit("parse"); + context->parser->ParseScript(unit.input, unit.options.GetCompilationMode() == CompilationMode::GEN_STD_LIB); + } // We have to check the return status of 'RunVerifierAndPhase` and 'RunPhases` separately because there can be // some internal errors (say, in Post-Conditional check) or terminate options (say in 'CheckOptionsAfterPhase') @@ -444,6 +488,7 @@ template allocator); @@ -492,6 +537,7 @@ static pandasm::Program *Compile(const CompilationUnit &unit, CompilerImpl *comp } MarkAsLowered(program); + ScopedTimer timeremit("emit"); return EmitProgram(compilerImpl, context, unit); } diff --git a/ets2panda/ir/astNode.cpp b/ets2panda/ir/astNode.cpp index 9661f1c36c..d90ad0478d 100644 --- a/ets2panda/ir/astNode.cpp +++ b/ets2panda/ir/astNode.cpp @@ -125,120 +125,6 @@ AstNode *AstNode::Clone([[maybe_unused]] ArenaAllocator *const allocator, [[mayb ES2PANDA_UNREACHABLE(); } -void AstNode::TransformChildrenRecursively(const NodeTransformer &cb, std::string_view transformationName) -{ // post-order, but use when you don't care about the order - TransformChildrenRecursivelyPostorder(cb, transformationName); -} - -void AstNode::TransformChildrenRecursively(const NodeTransformer &pre, const NodeTraverser &post, - std::string_view transformationName) -{ - TransformChildren( - [&pre, &post, transformationName](AstNode *child) { - auto *childReplacement = pre(child); - childReplacement->TransformChildrenRecursively(pre, post, transformationName); - post(childReplacement); - return childReplacement; - }, - transformationName); -} - -void AstNode::TransformChildrenRecursively(const NodeTraverser &pre, const NodeTransformer &post, - std::string_view transformationName) -{ - TransformChildren( - [&pre, &post, transformationName](AstNode *child) { - pre(child); - child->TransformChildrenRecursively(pre, post, transformationName); - return post(child); - }, - transformationName); -} - -void AstNode::TransformChildrenRecursivelyPreorder(const NodeTransformer &cb, std::string_view transformationName) -{ - TransformChildren( - [&cb, transformationName](AstNode *child) { - auto *res = cb(child); - res->TransformChildrenRecursivelyPreorder(cb, transformationName); - return res; - }, - transformationName); -} - -void AstNode::TransformChildrenRecursivelyPostorder(const NodeTransformer &cb, std::string_view transformationName) -{ - TransformChildren( - [&cb, transformationName](AstNode *child) { - child->TransformChildrenRecursivelyPostorder(cb, transformationName); - return cb(child); - }, - transformationName); -} - -void AstNode::IterateRecursively(const NodeTraverser &cb) const -{ // pre-order, use when you don't care - IterateRecursivelyPreorder(cb); -} - -void AstNode::IterateRecursivelyPreorder(const NodeTraverser &cb) const -{ - Iterate([&cb](AstNode *child) { - cb(child); - child->IterateRecursivelyPreorder(cb); - }); -} - -void AstNode::IterateRecursivelyPostorder(const NodeTraverser &cb) const -{ - Iterate([&cb](AstNode *child) { - child->IterateRecursivelyPostorder(cb); - cb(child); - }); -} - -void AnyChildHelper(bool *found, const NodePredicate &cb, AstNode *ast) -{ - if (*found) { - return; - } - - if (cb(ast)) { - *found = true; - return; - } - - ast->Iterate([&cb, found](AstNode *child) { AnyChildHelper(found, cb, child); }); -} - -bool AstNode::IsAnyChild(const NodePredicate &cb) const -{ - bool found = false; - Iterate([&found, cb](AstNode *child) { AnyChildHelper(&found, cb, child); }); - return found; -} - -void FindChildHelper(AstNode *&found, const NodePredicate &cb, AstNode *ast) -{ - if (found != nullptr) { - return; - } - - if (cb(ast)) { - found = ast; - return; - } - - ast->Iterate([&found, cb](AstNode *child) { FindChildHelper(found, cb, child); }); -} - -AstNode *AstNode::FindChild(const NodePredicate &cb) const -{ - AstNode *found = nullptr; - Iterate([&found, cb](AstNode *child) { FindChildHelper(found, cb, child); }); - return found; -} - varbinder::Scope *AstNode::EnclosingScope(const ir::AstNode *expr) noexcept { while (expr != nullptr && !expr->IsScopeBearer()) { @@ -381,16 +267,10 @@ compiler::PhaseId AstNode::GetFirstCreated() const return history_->FirstCreated(); } -AstNode *AstNode::GetHistoryNode() const +AstNode *AstNode::GetFromExistingHistory() const { - AstNode *node = nullptr; - - if (HistoryInitialized()) { - node = history_->Get(compiler::GetPhaseManager()->CurrentPhaseId()); - } else { - node = const_cast(this); - } - + ES2PANDA_ASSERT(HistoryInitialized()); + auto node = history_->Get(compiler::GetPhaseManager()->CurrentPhaseId()); ES2PANDA_ASSERT(node != nullptr); return node; } diff --git a/ets2panda/ir/astNode.h b/ets2panda/ir/astNode.h index 1fd37c07dd..adb4fcbe50 100644 --- a/ets2panda/ir/astNode.h +++ b/ets2panda/ir/astNode.h @@ -535,22 +535,95 @@ public: virtual void TransformChildren(const NodeTransformer &cb, std::string_view transformationName) = 0; virtual void Iterate(const NodeTraverser &cb) const = 0; - void TransformChildrenRecursively(const NodeTransformer &cb, std::string_view transformationName); - void TransformChildrenRecursively(const NodeTransformer &pre, const NodeTraverser &post, - std::string_view transformationName); - void TransformChildrenRecursively(const NodeTraverser &pre, const NodeTransformer &post, - std::string_view transformationName); - // CC-OFFNXT(C_RULE_ID_FUNCTION_HEADER, G.CMT.04) false positive - // Keep these for perf reasons: - void TransformChildrenRecursivelyPreorder(const NodeTransformer &cb, std::string_view transformationName); - void TransformChildrenRecursivelyPostorder(const NodeTransformer &cb, std::string_view transformationName); - - void IterateRecursively(const NodeTraverser &cb) const; - void IterateRecursivelyPreorder(const NodeTraverser &cb) const; - void IterateRecursivelyPostorder(const NodeTraverser &cb) const; - - bool IsAnyChild(const NodePredicate &cb) const; - AstNode *FindChild(const NodePredicate &cb) const; + template + void TransformChildrenRecursively(const F &cb, std::string_view transformationName) + { + TransformChildrenRecursivelyPostorder(cb, transformationName); + } + + template + void TransformChildrenRecursivelyPreorder(const F &cb, std::string_view transformationName) + { + std::function hcb = [&](AstNode *child) { + auto res = cb(child); + res->TransformChildren(hcb, transformationName); + return res; + }; + TransformChildren(hcb, transformationName); + } + + template + void TransformChildrenRecursivelyPostorder(const F &cb, std::string_view transformationName) + { + std::function hcb = [&](AstNode *child) { + child->TransformChildren(hcb, transformationName); + return cb(child); + }; + TransformChildren(hcb, transformationName); + } + + template + void TransformChildrenRecursively(const PRE &pre, const POST &post, std::string_view transformationName) + { + TransformChildren( + [&pre, &post, transformationName](AstNode *child) { + auto *childReplacement = pre(child); + childReplacement->TransformChildrenRecursively(pre, post, transformationName); + post(childReplacement); + return childReplacement; + }, + transformationName); + } + + template + void IterateRecursively(const F &cb) const + { + IterateRecursivelyPreorder(cb); + } + + template + void IterateRecursivelyPreorder(const F &cb) const + { + std::function hcb = [&](AstNode *child) { + cb(child); + child->Iterate(hcb); + }; + Iterate(hcb); + } + + template + void IterateRecursivelyPostorder(const F &cb) const + { + std::function hcb = [&](AstNode *child) { + child->Iterate(hcb); + cb(child); + }; + Iterate(hcb); + } + + template + AstNode *FindChild(const F &cb) const + { + AstNode *found = nullptr; + std::function hcb = [&](AstNode *child) { + if (found != nullptr) { + return; + } + if (cb(child)) { + found = child; + return; + } + child->Iterate(hcb); + }; + Iterate(hcb); + return found; + } + + template + bool IsAnyChild(const F &cb) const + { + return FindChild(cb) != nullptr; + } std::string DumpJSON() const; std::string DumpEtsSrc() const; @@ -585,7 +658,14 @@ public: bool IsValidInCurrentPhase() const; - AstNode *GetHistoryNode() const; + ALWAYS_INLINE AstNode *GetHistoryNode() const + { + if (UNLIKELY(history_ != nullptr)) { + return GetFromExistingHistory(); + } + return const_cast(this); + } + AstNode *GetOrCreateHistoryNode() const; protected: @@ -605,6 +685,8 @@ protected: void InitHistory(); bool HistoryInitialized() const; + AstNode *GetFromExistingHistory() const; + template T *GetHistoryNodeAs() const { diff --git a/ets2panda/util/generateBin.cpp b/ets2panda/util/generateBin.cpp index 33abd21586..73b0bf84dd 100644 --- a/ets2panda/util/generateBin.cpp +++ b/ets2panda/util/generateBin.cpp @@ -20,6 +20,8 @@ #include "compiler/compiler_options.h" #include "util/options.h" +#include "sys/resource.h" + namespace ark::es2panda::util { [[maybe_unused]] static void InitializeLogging(const util::Options &options) @@ -101,6 +103,39 @@ static int GenerateProgramImpl(ark::pandasm::Program *prog, const util::Options return 0; } +struct ScopedTimer { + ScopedTimer(std::string name_) + { + name = name_; + start = std::chrono::system_clock::now(); + srss = getrss(); + } + + static long getrss() + { + struct rusage ru; + if (getrusage(RUSAGE_SELF, &ru) != 0) { + abort(); + } + return ru.ru_maxrss; + } + + ~ScopedTimer() + { + auto end = std::chrono::system_clock::now(); + auto timeMs = std::chrono::duration_cast(end - start).count(); + if (timeMs == 0) { + return; + } + [[maybe_unused]] auto rss = (getrss() - srss); + // printf("@%-40s: %5zums %6ldkb\n", name.c_str(), timeMs, rss); + } + + std::string name; + std::chrono::time_point start; + long srss; +}; + int GenerateProgram(ark::pandasm::Program *prog, const util::Options &options, const ReporterFun &reporter) { std::map stat; @@ -109,10 +144,15 @@ int GenerateProgram(ark::pandasm::Program *prog, const util::Options &options, c ark::pandasm::AsmEmitter::PandaFileToPandaAsmMaps *mapsp = options.GetOptLevel() != 0 ? &maps : nullptr; #ifdef PANDA_WITH_BYTECODE_OPTIMIZER - if (OptimizeBytecode(prog, options, reporter, statp, mapsp) != 0) { - return 1; + { + ScopedTimer timerBco("optimize bytecode"); + if (OptimizeBytecode(prog, options, reporter, statp, mapsp) != 0) { + return 1; + } } #endif + + ScopedTimer timer("generate program "); if (GenerateProgramImpl(prog, options, reporter, statp, mapsp) != 0) { return 1; } -- Gitee From 317d8ac7ea77be6b1174cb99452c5c22c8dd9a70 Mon Sep 17 00:00:00 2001 From: Georgy Bronnikov Date: Thu, 12 Jun 2025 09:58:20 +0300 Subject: [PATCH 2/2] Improve type relation cache Signed-off-by: Georgy Bronnikov --- ets2panda/checker/types/typeRelation.cpp | 4 ---- ets2panda/checker/types/typeRelation.h | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/ets2panda/checker/types/typeRelation.cpp b/ets2panda/checker/types/typeRelation.cpp index 40a798ff92..f81f850ad4 100644 --- a/ets2panda/checker/types/typeRelation.cpp +++ b/ets2panda/checker/types/typeRelation.cpp @@ -29,10 +29,6 @@ ArenaAllocator *TypeRelation::Allocator() RelationResult TypeRelation::CacheLookup(const Type *source, const Type *target, const RelationHolder &holder, RelationType type) const { - if (result_ == RelationResult::CACHE_MISS) { - return result_; - } - ES2PANDA_ASSERT(source != nullptr); ES2PANDA_ASSERT(target != nullptr); diff --git a/ets2panda/checker/types/typeRelation.h b/ets2panda/checker/types/typeRelation.h index c5abfa2e85..a4fd9f17f0 100644 --- a/ets2panda/checker/types/typeRelation.h +++ b/ets2panda/checker/types/typeRelation.h @@ -95,7 +95,7 @@ class RelationKeyHasher { public: size_t operator()(const RelationKey &key) const noexcept { - return static_cast(key.sourceId ^ key.targetId); + return static_cast((key.sourceId << 32) ^ key.targetId); } }; -- Gitee