-
Notifications
You must be signed in to change notification settings - Fork 464
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3038 from mgreter/feature/custom-allocator
Add custom memory allocator (opt-in only so far) Might be activated by default on libsass version 3.7.
- Loading branch information
Showing
40 changed files
with
803 additions
and
158 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,34 @@ | ||
Welcome to the LibSass documentation! | ||
## LibSass documentation | ||
|
||
## First Off | ||
LibSass is just a library. To run the code locally (i.e. to compile your stylesheets), you need an implementer. SassC (get it?) is an implementer written in C. There are a number of other implementations of LibSass - for example Node. We encourage you to write your own port - the whole point of LibSass is that we want to bring Sass to many other languages, not just Ruby! | ||
LibSass is just a library. To run the code locally (i.e. to compile your | ||
stylesheets), you need an implementer. SassC is an implementer written in C. | ||
There are a number of other implementations of LibSass - for example NodeJS. | ||
We encourage you to write your own port - the whole point of LibSass is that | ||
we want to bring Sass to many other languages! | ||
|
||
We're working hard on moving to full parity with Ruby Sass... learn more at the [The-LibSass-Compatibility-Plan](compatibility-plan.md)! | ||
## LibSass road-map | ||
|
||
Since ruby-sass was retired in 2019 in favor of dart-sass, we slowly move | ||
toward full compatibility with the latest Sass specifications, although | ||
features like the module `@use` system may take a bit longer to add. | ||
|
||
### Implementing LibSass | ||
|
||
If you're interested in implementing LibSass in your own project see the [API Documentation](api-doc.md) which now includes implementing | ||
your own [Sass functions](api-function.md). You may wish to [look at other implementations](implementations.md) for your language of choice. | ||
Or make your own! | ||
If you're interested in implementing LibSass in your own project see the | ||
[API Documentation](api-doc.md) which now includes implementing your own | ||
[Sass functions](api-function.md). You may wish to [look at other | ||
implementations](implementations.md) for your language of choice. | ||
|
||
### Contributing to LibSass | ||
|
||
| Issue Tracker | Issue Triage | Community Guidelines | | ||
|-------------------|----------------------------------|-----------------------------| | ||
| We're always needing help, so check out our issue tracker, help some people out, and read our article on [Contributing](contributing.md)! It's got all the details on what to do! | To help understand the process of triaging bugs, have a look at our [Issue-Triage](triage.md) document. | Oh, and don't forget we always follow [Sass Community Guidelines](https://sass-lang.com/community-guidelines). Be nice and everyone else will be nice too! | | ||
|
||
### Building LibSass | ||
|
||
Please refer to the steps on [Building LibSass](build.md) | ||
|
||
### Developing LibSass | ||
|
||
Please refer to [Developing LibSass](developing.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
## Custom memory allocator | ||
|
||
LibSass comes with a custom memory allocator to improve performance. | ||
First included in LibSass 3.6 and currently disabled by default. | ||
Needs to be enabled by defining `SASS_CUSTOM_ALLOCATOR`. | ||
|
||
### Overview | ||
|
||
The allocator is a pool/arena allocator with a free-list on top. The | ||
memory usage pattern of LibSass fits this implementation very well. | ||
Every compilation tends to accumulate memory and only releasing some | ||
items from time to time, but the overall memory consumption will mostly | ||
grow until the compilation is finished. This helps us to keep the | ||
implementation as simple as possible, since we don't need to release | ||
much memory back to the system and can re-use it instead. | ||
|
||
### Arenas | ||
|
||
Each arena is allocated in a (compile time) fixed size. Every allocation | ||
request is served from the current arena. We basically slice up the | ||
arena into different sized chunks. Arenas are never returned to the | ||
system until the whole compilation is finished. | ||
|
||
### Slices | ||
|
||
A memory slice is a part of an arena. Once the system requests a sized | ||
memory chunk we check the current arena if there is enough space to | ||
hold it. If not a new arena is allocated. Then we return a pointer | ||
into that arena and mark the space as being used. Each slice also | ||
has a header which is invisible to the requester as it lies before | ||
the pointer address that we returned. This is used for book-keeping. | ||
|
||
### Free-lists (or buckets) | ||
|
||
Once a memory slice is returned to the allocator it will not be released. | ||
It will instead be put on the free list. We keep a fixed number of free lists, | ||
one for every possible chunk size. Since chunk sizes are memory aligned, we | ||
can get the free-list index (aka `bucket`) very quickly (`size/alignment`). | ||
For further readings see https://en.wikipedia.org/wiki/Free_list. | ||
|
||
### Chunk-sizes | ||
|
||
Since arenas are of fixed size we need to make sure that only small | ||
enough chunks get served from it. This also helps to keep implementation | ||
simple, as we can statically declare some structures for book-keeping. | ||
Allocations that are too big to be tracked on a free-list will be patched | ||
directly to malloc and free. This is the case when the bucket index would | ||
be bigger than `SassAllocatorBuckets`. | ||
|
||
### Thread-safety | ||
|
||
This implementation is not thread-safe by design. Making it thread-safe | ||
would certainly be possible, but it would come at a (performance) price. | ||
Also it is not needed given the memory usage pattern of LibSass. Instead | ||
we should make sure that memory pools are local to each thread. | ||
|
||
### Implementation obstacles | ||
|
||
Since memory allocation is a core part of C++ itself, we get into various | ||
difficult territories. This has specially proven true in regard of static | ||
variable initialization and destruction order. E.g when we have a static | ||
string with custom allocator. It might be that it is initialized before | ||
the thread local memory pool. On the other hand it's also possible that | ||
the memory pool is destroyed before another static string wants to give | ||
back its memory to the pool. I tried hard to work around those issues. | ||
Mainly by only using thead local POD (plain old data) objects. | ||
|
||
See https://isocpp.org/wiki/faq/ctors#static-init-order | ||
|
||
### Performance gains | ||
|
||
My tests indicate that the custom allocator brings around 15% performance | ||
enhancement for complex cases (I used the bolt-bench to measure it). Once | ||
more get optimized, the custom allocator can bring up to 30% improvement. | ||
This comes at a small cost of a few percent of overall memory usage. This | ||
can be tweaked, but the sweet spot for now seems to be: | ||
|
||
```c | ||
// How many buckets should we have for the free-list | ||
// Determines when allocations go directly to malloc/free | ||
// For maximum size of managed items multiply by alignment | ||
#define SassAllocatorBuckets 512 | ||
|
||
// The size of the memory pool arenas in bytes. | ||
#define SassAllocatorArenaSize (1024 * 256) | ||
``` | ||
These can be found in `settings.hpp`. | ||
### Memory overhead | ||
Both settings `SassAllocatorBuckets` and `SassAllocatorArenaSize` need | ||
to be set in relation to each other. Assuming the memory alignment on | ||
the platform is 8 bytes, the maximum chunk size that can be handled | ||
is 4KB (512*8B). If the arena size is too close to this value, you | ||
may leave a lot of RAM unused. Once an arena can't fullfil the current | ||
request, it is put away and a new one is allocated. We don't keep track | ||
of unused space in previous arenas, as it bloats the code and costs | ||
precious cpu time. By setting the values carefully we can avoid the cost | ||
and still provide reasonable memory overhead. In the worst scenario we | ||
loose around 1.5% for the default settings (4K of 256K). | ||
### Further improvements | ||
It is worth to check if we can re-use the space of old arenas somehow without | ||
scarifying to much performance. Additionally we could check free-lists of | ||
bigger chunks sizes to satisfy an allocation request. But both would need | ||
to be checked for performance impact and their actual gain. |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
## Developing LibSass | ||
|
||
So far this is only a loose collection of developer relevant docs: | ||
|
||
- [Building LibSass](build.md) | ||
- [Profiling LibSass](dev-profiling.md) | ||
- [C-API documentation](api-doc.md) | ||
- [LibSass and Unicode](unicode.md) | ||
- [SourceMap internals](source-map-internals.md) | ||
- [Custom memory allocator](allocator.md) | ||
- [Smart pointer implementation](dev-ast-memory.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
//----------------------------------------------------------------------------- | ||
// MurmurHash2 was written by Austin Appleby, and is placed in the public | ||
// domain. The author hereby disclaims copyright to this source code. | ||
//----------------------------------------------------------------------------- | ||
// LibSass only needs MurmurHash2, so we made this header only | ||
//----------------------------------------------------------------------------- | ||
|
||
#ifndef _MURMURHASH2_H_ | ||
#define _MURMURHASH2_H_ | ||
|
||
//----------------------------------------------------------------------------- | ||
// Platform-specific functions and macros | ||
|
||
// Microsoft Visual Studio | ||
|
||
#if defined(_MSC_VER) && (_MSC_VER < 1600) | ||
|
||
typedef unsigned char uint8_t; | ||
typedef unsigned int uint32_t; | ||
typedef unsigned __int64 uint64_t; | ||
|
||
// Other compilers | ||
|
||
#else // defined(_MSC_VER) | ||
|
||
#include <stdint.h> | ||
|
||
#endif // !defined(_MSC_VER) | ||
|
||
//----------------------------------------------------------------------------- | ||
|
||
inline uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ) | ||
{ | ||
// 'm' and 'r' are mixing constants generated offline. | ||
// They're not really 'magic', they just happen to work well. | ||
|
||
const uint32_t m = 0x5bd1e995; | ||
const int r = 24; | ||
|
||
// Initialize the hash to a 'random' value | ||
|
||
uint32_t h = seed ^ len; | ||
|
||
// Mix 4 bytes at a time into the hash | ||
|
||
const unsigned char * data = (const unsigned char *)key; | ||
|
||
while(len >= 4) | ||
{ | ||
uint32_t k = *(uint32_t*)data; | ||
|
||
k *= m; | ||
k ^= k >> r; | ||
k *= m; | ||
|
||
h *= m; | ||
h ^= k; | ||
|
||
data += 4; | ||
len -= 4; | ||
} | ||
|
||
// Handle the last few bytes of the input array | ||
|
||
switch(len) | ||
{ | ||
case 3: | ||
h ^= data[2] << 16; | ||
/* fall through */ | ||
case 2: | ||
h ^= data[1] << 8; | ||
/* fall through */ | ||
case 1: | ||
h ^= data[0]; | ||
h *= m; | ||
}; | ||
|
||
// Do a few final mixes of the hash to ensure the last few | ||
// bytes are well-incorporated. | ||
|
||
h ^= h >> 13; | ||
h *= m; | ||
h ^= h >> 15; | ||
|
||
return h; | ||
} | ||
|
||
//----------------------------------------------------------------------------- | ||
|
||
#endif // _MURMURHASH2_H_ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.