Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: small-string optimization #9895

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
52 changes: 35 additions & 17 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -873,34 +873,51 @@ DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
evalState.runDebugRepl(nullptr, trace.env, trace.expr);
}

unsigned long nrSmallStrings = 0;
void Value::mkString(std::string_view s)
{
mkString(makeImmutableString(s));
if (s.length() < (sizeof(char *) * 2)) {
nrSmallStrings++;
mkString(s.data(),s.length());
} else {
mkString(makeImmutableString(s),s.length());
}
}


static void copyContextToValue(Value & v, const NixStringContext & context)
{
if (!context.empty()) {
size_t n = 0;
v.string.context = (const char * *)
allocBytes((context.size() + 1) * sizeof(char *));
for (auto & i : context)
v.string.context[n++] = dupString(i.to_string().c_str());
v.string.context[n] = 0;
}
size_t n = 0;
v.string.context = (const char * *)
allocBytes((context.size() + 1) * sizeof(char *));
for (auto & i : context)
v.string.context[n++] = dupString(i.to_string().c_str());
v.string.context[n] = 0;
}

void Value::mkString(std::string_view s, const NixStringContext & context)
{
mkString(s);
copyContextToValue(*this, context);
if (context.empty() && s.length() < (sizeof(char *) * 2)) {
nrSmallStrings++;
mkString(s);
}
else if (context.empty()){
mkString(s);
} else {
mkString(makeImmutableString(s),s.length(),0,true);
copyContextToValue(*this, context);
}
}

void Value::mkStringMove(const char * s, const NixStringContext & context)
void Value::mkStringMove(std::string_view s, const NixStringContext & context)
{
mkString(s);
copyContextToValue(*this, context);
if (context.empty()){
// already allocated
mkString(s.data(),s.length(),0,true);
} else {
mkString(s.data(),s.length(),0,true);
copyContextToValue(*this, context);
}
}


Expand Down Expand Up @@ -1965,7 +1982,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
tmp += part->size();
}
*tmp = 0;
return result;
return std::string_view(result, sSize);
};

// List of returned strings. References to these Values must NOT be persisted.
Expand Down Expand Up @@ -2189,8 +2206,8 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string

void copyContext(const Value & v, NixStringContext & context)
{
if (v.string.context)
for (const char * * p = v.string.context; *p; ++p)
if (v.context() != 0)
for (const char * * p = v.context(); *p; ++p)
context.insert(NixStringContextElem::parse(*p));
}

Expand Down Expand Up @@ -2616,6 +2633,7 @@ void EvalState::printStatistics()
topObj["nrOpUpdates"] = nrOpUpdates;
topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied;
topObj["nrThunks"] = nrThunks;
topObj["nrSmallStrings"] = nrSmallStrings;
topObj["nrAvoided"] = nrAvoided;
topObj["nrLookups"] = nrLookups;
topObj["nrPrimOpCalls"] = nrPrimOpCalls;
Expand Down
42 changes: 35 additions & 7 deletions src/libexpr/value.hh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ typedef enum {
tInt = 1,
tBool,
tString,
tStringSmall,
tPath,
tNull,
tAttrs,
Expand Down Expand Up @@ -185,6 +186,10 @@ public:
const char * c_str;
const char * * context; // must be in sorted order
};
struct SmallStringWithoutContext {
char * c_str;
char * c_str_2;
};

struct Path {
InputAccessor * accessor;
Expand All @@ -211,6 +216,7 @@ public:
bool boolean;

StringWithContext string;
SmallStringWithoutContext smallString;

Path _path;

Expand Down Expand Up @@ -242,6 +248,7 @@ public:
case tInt: return nInt;
case tBool: return nBool;
case tString: return nString;
case tStringSmall: return nString;
case tPath: return nPath;
case tNull: return nNull;
case tAttrs: return nAttrs;
Expand Down Expand Up @@ -280,6 +287,18 @@ public:
boolean = b;
}

// Assumption that the string s is null-terminated
// assert(s[len] == '\0');
inline void mkString(const char * s, size_t len, const char * * context = 0, bool forceLarge = false){
if (!forceLarge && len < (sizeof(char *) *2) && context == 0){
internalType = tStringSmall;
char * t = (char *) &(smallString.c_str);
memcpy(t, s, len);
t[len] = '\0';
return;
}
mkString(s, context);
}
inline void mkString(const char * s, const char * * context = 0)
{
internalType = tString;
Expand All @@ -291,11 +310,11 @@ public:

void mkString(std::string_view s, const NixStringContext & context);

void mkStringMove(const char * s, const NixStringContext & context);
void mkStringMove(std::string_view s, const NixStringContext & context);

inline void mkString(const Symbol & s)
{
mkString(((const std::string &) s).c_str());
mkString((const std::string &) s);
}

void mkPath(const SourcePath & path);
Expand Down Expand Up @@ -432,19 +451,28 @@ public:

std::string_view string_view() const
{
assert(internalType == tString);
return std::string_view(string.c_str);
assert(internalType == tString || internalType == tStringSmall);
if (internalType == tStringSmall)
return std::string_view((const char * const) &(smallString.c_str));
else
return std::string_view(string.c_str);
}

const char * const c_str() const
{
assert(internalType == tString);
return string.c_str;
assert(internalType == tString || internalType == tStringSmall);
if (internalType == tStringSmall)
return (const char * const) &(smallString.c_str);
else
return string.c_str;
}

const char * * context() const
{
return string.context;
if (internalType == tString)
return string.context;
else
return 0;
}
};

Expand Down
Loading