45.2 SmallSUT
SmallSUT is an implementation of the pure abstract interface ISUT
when the entire SUT can be stored in the root block.
Representation
SmallSUT stores one 32-bit utilisation for every segment in a contiguous in-memory vector. The vector is serialized directly into each new root-block division during a checkpoint, so no separately allocated SUT-section segments are required. Lookups and updates are constant-time array operations.
Transition to LargeSUT
The transition is governed by the space required to serialize the SmallSUT into a root-block
division. It is not triggered directly by allocating a segment and there is no separately maintained
maximum segment count. Instead, the SUT facade tests the serialized size
when SUT::WriteDirtySUTSections() is called during preparation for a checkpoint. It tests
the size of the complete SUT representation that would be written to the root block:
sizeof(int) + sizeof(int64) + sizeof(bool) + small_.GetSerialisationSize()
The first three terms account for the SUT schema, total utilisation and the flag identifying which
implementation is active. SmallSUT::GetSerialisationSize() adds the SmallSUT schema, the
serialized vector length, and one int utilisation value for every vector entry:
sizeof(int) + sizeof(int) + numSegments * sizeof(int)
Conversion takes place when the resulting size is greater than
MAX_SUT_SIZE_IN_ROOT_BLOCK. The current default for this limit is 20 KiB. This is a
deliberately conservative limit: the implementation notes that more root-block space is reserved,
but uses 20 KiB so that it changes to separately stored SUT sections earlier. The limit can also be
reduced by the stress-test program to exercise the transition more frequently. Thus the effective
maximum size of a SmallSUT follows from its serialized representation and this configured byte
limit; it is not an independent architectural limit on the number of segments.
With the current data types, the expression has a fixed overhead of 21 bytes and requires four bytes for each entry in the utilisation vector. The first vector size for which the expression exceeds 20 KiB is therefore 5,115 entries:
21 + 4 * 5115 = 20481 > 20 * 1024
Vector entry zero does not represent a store segment, so 5,115 entries correspond to 5,114 segments. The following table gives the resulting file size at the earliest checkpoint which can cause the transition. It includes the 64 KiB root block in addition to the segments.
| Segment size | Number of segments | Store size |
|---|---|---|
| 512 KiB | 5,114 | 2,557.0625 MiB (approximately 2.497 GiB) |
| 1 MiB | 5,114 | 5,114.0625 MiB (approximately 4.994 GiB) |
| 2 MiB | 5,114 | 10,228.0625 MiB (approximately 9.988 GiB) |
| 4 MiB | 5,114 | 20,456.0625 MiB (approximately 19.977 GiB) |
Because the test occurs during checkpoint preparation, allocating the segment that takes the
SmallSUT over the limit does not itself perform the conversion. The in-memory SmallSUT may exceed
the limit temporarily. Consequently, if multiple segments are allocated before the next checkpoint,
the actual store can be larger than the values in the table when conversion occurs. At the subsequent
call to WriteDirtySUTSections(), the facade
initializes LargeSUT from all the entries in SmallSUT, clears the
SmallSUT vector, and changes its current implementation to LargeSUT. It then writes any dirty
LargeSUT sections required by that checkpoint.
The transition is one way. Once LargeSUT has been selected, the size test is no longer performed and the store does not revert to SmallSUT, even if high-numbered segments later become free. This avoids changing representations merely because current utilisation falls, and ensures that a store whose segment address space has grown beyond the root-block representation continues to use the scalable on-disk representation.
Code
class SmallSUT : public ISUT
{
public:
SmallSUT() {}
void Clear();
virtual void Serialise(Archive& ar) const;
virtual void Deserialise(InputArchive& ar);
virtual int GetUtilisation(SegId segid) const;
virtual void SetUtilisation(SegId segid, int u);
virtual int GetTotalNumSegments() const;
virtual void IncrementTotalNumSegments();
int GetSerialisationSize() const;
private:
xvector<int> utilisations_;
};