Consider (re)using precompiled Int-Closures for different types.
Motivation
GHC currently precompiles Int and Char closures for the range [0/-16 .. 255].
This is quite beneficial as we don't need to scavenge these static closures. For this reason the runtime replaces and dynamically created Int/Char closure in this value range with it's static equivalent.
The obvious problem here is only Int and Char types get this benefit. This means if we change a type from Int
to Word
we might suddenly have higher residency and GC load.
The same holds for Int8
, Int16
, Int32
, ... and also user defined types of the sort `data T = MyInt !Int | Undefined.
Expanding the value range had surprisingly large effects in !1464 (closed) so this might too.
Details
The actual precompiled valus are trivial, consisting only of a pointer to the info table of the closure and the payload (the value).
The main question now is, can we replace the info table pointer from the Word
closure to the one for Int
without any negative side effects?
Is this feasible:
The info table contains:
-
A reference to the name of the constructor.
-
Closure Layout
-
Closure Type
-
The zero based constructor tag
-
The names are not exposed to haskell code/users for non-profiling builds and as such not a problem.
-
Closure layout is the same for all these closures (One word of non-pointer payload)
-
Closure type is the same for all of these (CONSTR_0_1)
-
The tag field could be different for user-defined types. But it seems unused currently.
Proposal
Currently we replace closures with precompiled ones like this:
StgWord w = (StgWord)q->payload[0];
if (info == Czh_con_info &&
// unsigned, so always true: (StgChar)w >= MIN_CHARLIKE &&
(StgChar)w <= MAX_CHARLIKE) {
*p = TAG_CLOSURE(tag,
(StgClosure *)CHARLIKE_CLOSURE((StgChar)w)
);
}
else if (info == Izh_con_info &&
(StgInt)w >= MIN_INTLIKE && (StgInt)w <= MAX_INTLIKE) {
*p = TAG_CLOSURE(tag,
(StgClosure *)INTLIKE_CLOSURE((StgInt)w)
);
}
else {
copy_tag_nolock(p,info,q,sizeofW(StgHeader)+1,gen_no,tag);
}
Why could this ever work.
All the essential info table fields are the same except:
- The constructor name, which is not used unless explicitly via things like heap view or profiling
- The constructor tag, on which any replacement will have to agree on.
This could be implemented for the unprofiled case as something like:
StgWord w = (StgWord)q->payload[0];
if ((StgInt)w >= MIN_PRECOMPILED && (StgInt)w <= MAX_PRECOMPILED &&
(GET_TAG(q) == 1) {
*p = TAG_CLOSURE(tag,
(StgClosure *)PRECOMPILED_CLOSURE((StgInt)w)
);
}
else {
copy_tag_nolock(p,info,q,sizeofW(StgHeader)+1,gen_no,tag);
}
The downside is that heap-profiling builds lose some precision as we could at best report something like "INT-LIKE Static" for those closures.
It shouldn't take more than a week to implement and test this. But I did not get around to it yet.