llvm-project/compiler-rt/lib/scudo/standalone/primary64.h

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

490 lines
18 KiB
C
Raw Normal View History

//===-- primary64.h ---------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_PRIMARY64_H_
#define SCUDO_PRIMARY64_H_
#include "bytemap.h"
#include "common.h"
#include "list.h"
#include "local_cache.h"
#include "memtag.h"
#include "options.h"
#include "release.h"
#include "stats.h"
#include "string_utils.h"
namespace scudo {
// SizeClassAllocator64 is an allocator tuned for 64-bit address space.
//
// It starts by reserving NumClasses * 2^RegionSizeLog bytes, equally divided in
// Regions, specific to each size class. Note that the base of that mapping is
// random (based to the platform specific map() capabilities). If
// PrimaryEnableRandomOffset is set, each Region actually starts at a random
// offset from its base.
//
// Regions are mapped incrementally on demand to fulfill allocation requests,
// those mappings being split into equally sized Blocks based on the size class
// they belong to. The Blocks created are shuffled to prevent predictable
// address patterns (the predictability increases with the size of the Blocks).
//
// The 1st Region (for size class 0) holds the TransferBatches. This is a
// structure used to transfer arrays of available pointers from the class size
// freelist to the thread specific freelist, and back.
//
// The memory used by this allocator is never unmapped, but can be partially
// released if the platform allows for it.
template <typename Config> class SizeClassAllocator64 {
public:
typedef typename Config::PrimaryCompactPtrT CompactPtrT;
static const uptr CompactPtrScale = Config::PrimaryCompactPtrScale;
typedef typename Config::SizeClassMap SizeClassMap;
typedef SizeClassAllocator64<Config> ThisT;
typedef SizeClassAllocatorLocalCache<ThisT> CacheT;
typedef typename CacheT::TransferBatch TransferBatch;
static uptr getSizeByClassId(uptr ClassId) {
return (ClassId == SizeClassMap::BatchClassId)
? roundUpTo(sizeof(TransferBatch), 1U << CompactPtrScale)
: SizeClassMap::getSizeByClassId(ClassId);
}
static bool canAllocate(uptr Size) { return Size <= SizeClassMap::MaxSize; }
void init(s32 ReleaseToOsInterval) {
DCHECK(isAligned(reinterpret_cast<uptr>(this), alignof(ThisT)));
DCHECK_EQ(PrimaryBase, 0U);
// Reserve the space required for the Primary.
PrimaryBase = reinterpret_cast<uptr>(
scudo: Support memory tagging in the secondary allocator. This patch enhances the secondary allocator to be able to detect buffer overflow, and (on hardware supporting memory tagging) use-after-free and buffer underflow. Use-after-free detection is implemented by setting memory page protection to PROT_NONE on free. Because this must be done immediately rather than after the memory has been quarantined, we no longer use the combined allocator quarantine for secondary allocations. Instead, a quarantine has been added to the secondary allocator cache. Buffer overflow detection is implemented by aligning the allocation to the right of the writable pages, so that any overflows will spill into the guard page to the right of the allocation, which will have PROT_NONE page protection. Because this would require the secondary allocator to produce a header at the correct position, the responsibility for ensuring chunk alignment has been moved to the secondary allocator. Buffer underflow detection has been implemented on hardware supporting memory tagging by tagging the memory region between the start of the mapping and the start of the allocation with a non-zero tag. Due to the cost of pre-tagging secondary allocations and the memory bandwidth cost of tagged accesses, the allocation itself uses a tag of 0 and only the first four pages have memory tagging enabled. This is a reland of commit 7a0da8894348 which was reverted in commit 9678b07e42ee. This reland includes the following changes: - Fix the calculation of BlockSize which led to incorrect statistics returned by mallinfo(). - Add -Wno-pedantic to silence GCC warning. - Optionally add some slack at the end of secondary allocations to help work around buggy applications that read off the end of their allocation. Differential Revision: https://reviews.llvm.org/D93731
2020-12-22 10:39:03 +08:00
map(nullptr, PrimarySize, nullptr, MAP_NOACCESS, &Data));
u32 Seed;
const u64 Time = getMonotonicTime();
if (!getRandom(reinterpret_cast<void *>(&Seed), sizeof(Seed)))
Seed = static_cast<u32>(Time ^ (PrimaryBase >> 12));
const uptr PageSize = getPageSizeCached();
for (uptr I = 0; I < NumClasses; I++) {
RegionInfo *Region = getRegionInfo(I);
// The actual start of a region is offset by a random number of pages
// when PrimaryEnableRandomOffset is set.
Region->RegionBeg = getRegionBaseByClassId(I) +
(Config::PrimaryEnableRandomOffset
? ((getRandomModN(&Seed, 16) + 1) * PageSize)
: 0);
Region->RandState = getRandomU32(&Seed);
Region->ReleaseInfo.LastReleaseAtNs = Time;
}
setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval));
}
void unmapTestOnly() {
for (uptr I = 0; I < NumClasses; I++) {
RegionInfo *Region = getRegionInfo(I);
*Region = {};
}
unmap(reinterpret_cast<void *>(PrimaryBase), PrimarySize, UNMAP_ALL, &Data);
PrimaryBase = 0U;
}
TransferBatch *popBatch(CacheT *C, uptr ClassId) {
DCHECK_LT(ClassId, NumClasses);
RegionInfo *Region = getRegionInfo(ClassId);
ScopedLock L(Region->Mutex);
TransferBatch *B = Region->FreeList.front();
[scudo][standalone] Optimization pass Summary: This introduces a bunch of small optimizations with the purpose of making the fastpath tighter: - tag more conditions as `LIKELY`/`UNLIKELY`: as a rule of thumb we consider that every operation related to the secondary is unlikely - attempt to reduce the number of potentially extraneous instructions - reorganize the `Chunk` header to not straddle a word boundary and use more appropriate types Note that some `LIKELY`/`UNLIKELY` impact might be less obvious as they are in slow paths (for example in `secondary.cc`), but at this point I am throwing a pretty wide net, and it's consistant and doesn't hurt. This was mosly done for the benfit of Android, but other platforms benefit from it too. An aarch64 Android benchmark gives: - before: ``` BM_youtube/min_time:15.000/repeats:4/manual_time_mean 445244 us 659385 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_median 445007 us 658970 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_stddev 885 us 1332 us 4 ``` - after: ``` BM_youtube/min_time:15.000/repeats:4/manual_time_mean 415697 us 621925 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_median 415913 us 622061 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_stddev 990 us 1163 us 4 ``` Additional since `-Werror=conversion` is enabled on some platforms we are built on, enable it upstream to catch things early: a few sign conversions had slept through and needed additional casting. Reviewers: hctim, morehouse, eugenis, vitalybuka Reviewed By: vitalybuka Subscribers: srhines, mgorny, javed.absar, kristof.beyls, delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D64664 llvm-svn: 366918
2019-07-25 00:36:01 +08:00
if (B) {
Region->FreeList.pop_front();
[scudo][standalone] Optimization pass Summary: This introduces a bunch of small optimizations with the purpose of making the fastpath tighter: - tag more conditions as `LIKELY`/`UNLIKELY`: as a rule of thumb we consider that every operation related to the secondary is unlikely - attempt to reduce the number of potentially extraneous instructions - reorganize the `Chunk` header to not straddle a word boundary and use more appropriate types Note that some `LIKELY`/`UNLIKELY` impact might be less obvious as they are in slow paths (for example in `secondary.cc`), but at this point I am throwing a pretty wide net, and it's consistant and doesn't hurt. This was mosly done for the benfit of Android, but other platforms benefit from it too. An aarch64 Android benchmark gives: - before: ``` BM_youtube/min_time:15.000/repeats:4/manual_time_mean 445244 us 659385 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_median 445007 us 658970 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_stddev 885 us 1332 us 4 ``` - after: ``` BM_youtube/min_time:15.000/repeats:4/manual_time_mean 415697 us 621925 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_median 415913 us 622061 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_stddev 990 us 1163 us 4 ``` Additional since `-Werror=conversion` is enabled on some platforms we are built on, enable it upstream to catch things early: a few sign conversions had slept through and needed additional casting. Reviewers: hctim, morehouse, eugenis, vitalybuka Reviewed By: vitalybuka Subscribers: srhines, mgorny, javed.absar, kristof.beyls, delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D64664 llvm-svn: 366918
2019-07-25 00:36:01 +08:00
} else {
B = populateFreeList(C, ClassId, Region);
if (UNLIKELY(!B))
return nullptr;
}
DCHECK_GT(B->getCount(), 0);
Region->Stats.PoppedBlocks += B->getCount();
return B;
}
void pushBatch(uptr ClassId, TransferBatch *B) {
DCHECK_GT(B->getCount(), 0);
RegionInfo *Region = getRegionInfo(ClassId);
ScopedLock L(Region->Mutex);
Region->FreeList.push_front(B);
Region->Stats.PushedBlocks += B->getCount();
if (ClassId != SizeClassMap::BatchClassId)
releaseToOSMaybe(Region, ClassId);
}
void disable() {
// The BatchClassId must be locked last since other classes can use it.
for (sptr I = static_cast<sptr>(NumClasses) - 1; I >= 0; I--) {
if (static_cast<uptr>(I) == SizeClassMap::BatchClassId)
continue;
getRegionInfo(static_cast<uptr>(I))->Mutex.lock();
}
getRegionInfo(SizeClassMap::BatchClassId)->Mutex.lock();
}
void enable() {
getRegionInfo(SizeClassMap::BatchClassId)->Mutex.unlock();
for (uptr I = 0; I < NumClasses; I++) {
if (I == SizeClassMap::BatchClassId)
continue;
getRegionInfo(I)->Mutex.unlock();
}
}
template <typename F> void iterateOverBlocks(F Callback) {
for (uptr I = 0; I < NumClasses; I++) {
if (I == SizeClassMap::BatchClassId)
continue;
const RegionInfo *Region = getRegionInfo(I);
const uptr BlockSize = getSizeByClassId(I);
const uptr From = Region->RegionBeg;
const uptr To = From + Region->AllocatedUser;
for (uptr Block = From; Block < To; Block += BlockSize)
Callback(Block);
}
}
void getStats(ScopedString *Str) {
// TODO(kostyak): get the RSS per region.
uptr TotalMapped = 0;
uptr PoppedBlocks = 0;
uptr PushedBlocks = 0;
for (uptr I = 0; I < NumClasses; I++) {
RegionInfo *Region = getRegionInfo(I);
if (Region->MappedUser)
TotalMapped += Region->MappedUser;
PoppedBlocks += Region->Stats.PoppedBlocks;
PushedBlocks += Region->Stats.PushedBlocks;
}
Str->append("Stats: SizeClassAllocator64: %zuM mapped (%uM rss) in %zu "
"allocations; remains %zu\n",
TotalMapped >> 20, 0U, PoppedBlocks,
PoppedBlocks - PushedBlocks);
for (uptr I = 0; I < NumClasses; I++)
getStats(Str, I, 0);
}
bool setOption(Option O, sptr Value) {
if (O == Option::ReleaseInterval) {
const s32 Interval = Max(
Min(static_cast<s32>(Value), Config::PrimaryMaxReleaseToOsIntervalMs),
Config::PrimaryMinReleaseToOsIntervalMs);
atomic_store_relaxed(&ReleaseToOsIntervalMs, Interval);
return true;
}
// Not supported by the Primary, but not an error either.
return true;
}
[scudo][standalone] Correct releaseToOS behavior Summary: There was an issue in `releaseToOSMaybe`: one of the criteria to decide if we should proceed with the release was wrong. Namely: ``` const uptr N = Sci->Stats.PoppedBlocks - Sci->Stats.PushedBlocks; if (N * BlockSize < PageSize) return; // No chance to release anything. ``` I meant to check if the amount of bytes in the free list was lower than a page, but this actually checks if the amount of **in use** bytes was lower than a page. The correct code is: ``` const uptr BytesInFreeList = Region->AllocatedUser - (Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize; if (BytesInFreeList < PageSize) return 0; // No chance to release anything. ``` Consequences of the bug: - if a class size has less than a page worth of in-use bytes (allocated or in a cache), reclaiming would not occur, whatever the amount of blocks in the free list; in real world scenarios this is unlikely to happen and be impactful; - if a class size had less than a page worth of free bytes (and enough in-use bytes, etc), then reclaiming would be attempted, with likely no result. This means the reclaiming was overzealous at times. I didn't have a good way to test for this, so I changed the prototype of the function to return the number of bytes released, allowing to get the information needed. The test added fails with the initial criteria. Another issue is that `ReleaseToOsInterval` can actually be 0, meaning we always try to release (side note: it's terrible for performances). so change a `> 0` check to `>= 0`. Additionally, decrease the `CanRelease` threshold to `PageSize / 32`. I still have to make that configurable but I will do it at another time. Finally, rename some variables in `printStats`: I feel like "available" was too ambiguous, so change it to "total". Reviewers: morehouse, hctim, eugenis, vitalybuka, cferris Reviewed By: morehouse Subscribers: delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D68471 llvm-svn: 373930
2019-10-08 01:37:39 +08:00
uptr releaseToOS() {
uptr TotalReleasedBytes = 0;
for (uptr I = 0; I < NumClasses; I++) {
if (I == SizeClassMap::BatchClassId)
continue;
RegionInfo *Region = getRegionInfo(I);
ScopedLock L(Region->Mutex);
[scudo][standalone] Correct releaseToOS behavior Summary: There was an issue in `releaseToOSMaybe`: one of the criteria to decide if we should proceed with the release was wrong. Namely: ``` const uptr N = Sci->Stats.PoppedBlocks - Sci->Stats.PushedBlocks; if (N * BlockSize < PageSize) return; // No chance to release anything. ``` I meant to check if the amount of bytes in the free list was lower than a page, but this actually checks if the amount of **in use** bytes was lower than a page. The correct code is: ``` const uptr BytesInFreeList = Region->AllocatedUser - (Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize; if (BytesInFreeList < PageSize) return 0; // No chance to release anything. ``` Consequences of the bug: - if a class size has less than a page worth of in-use bytes (allocated or in a cache), reclaiming would not occur, whatever the amount of blocks in the free list; in real world scenarios this is unlikely to happen and be impactful; - if a class size had less than a page worth of free bytes (and enough in-use bytes, etc), then reclaiming would be attempted, with likely no result. This means the reclaiming was overzealous at times. I didn't have a good way to test for this, so I changed the prototype of the function to return the number of bytes released, allowing to get the information needed. The test added fails with the initial criteria. Another issue is that `ReleaseToOsInterval` can actually be 0, meaning we always try to release (side note: it's terrible for performances). so change a `> 0` check to `>= 0`. Additionally, decrease the `CanRelease` threshold to `PageSize / 32`. I still have to make that configurable but I will do it at another time. Finally, rename some variables in `printStats`: I feel like "available" was too ambiguous, so change it to "total". Reviewers: morehouse, hctim, eugenis, vitalybuka, cferris Reviewed By: morehouse Subscribers: delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D68471 llvm-svn: 373930
2019-10-08 01:37:39 +08:00
TotalReleasedBytes += releaseToOSMaybe(Region, I, /*Force=*/true);
}
[scudo][standalone] Correct releaseToOS behavior Summary: There was an issue in `releaseToOSMaybe`: one of the criteria to decide if we should proceed with the release was wrong. Namely: ``` const uptr N = Sci->Stats.PoppedBlocks - Sci->Stats.PushedBlocks; if (N * BlockSize < PageSize) return; // No chance to release anything. ``` I meant to check if the amount of bytes in the free list was lower than a page, but this actually checks if the amount of **in use** bytes was lower than a page. The correct code is: ``` const uptr BytesInFreeList = Region->AllocatedUser - (Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize; if (BytesInFreeList < PageSize) return 0; // No chance to release anything. ``` Consequences of the bug: - if a class size has less than a page worth of in-use bytes (allocated or in a cache), reclaiming would not occur, whatever the amount of blocks in the free list; in real world scenarios this is unlikely to happen and be impactful; - if a class size had less than a page worth of free bytes (and enough in-use bytes, etc), then reclaiming would be attempted, with likely no result. This means the reclaiming was overzealous at times. I didn't have a good way to test for this, so I changed the prototype of the function to return the number of bytes released, allowing to get the information needed. The test added fails with the initial criteria. Another issue is that `ReleaseToOsInterval` can actually be 0, meaning we always try to release (side note: it's terrible for performances). so change a `> 0` check to `>= 0`. Additionally, decrease the `CanRelease` threshold to `PageSize / 32`. I still have to make that configurable but I will do it at another time. Finally, rename some variables in `printStats`: I feel like "available" was too ambiguous, so change it to "total". Reviewers: morehouse, hctim, eugenis, vitalybuka, cferris Reviewed By: morehouse Subscribers: delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D68471 llvm-svn: 373930
2019-10-08 01:37:39 +08:00
return TotalReleasedBytes;
}
const char *getRegionInfoArrayAddress() const {
return reinterpret_cast<const char *>(RegionInfoArray);
}
[scudo][standalone] Release smaller blocks less often Summary: Releasing smaller blocks is costly and only yields significant results when there is a large percentage of free bytes for a given size class (see numbers below). This CL introduces a couple of additional checks for sizes lower than 256. First we want to make sure that there is enough free bytes, relatively to the amount of allocated bytes. We are looking at 8X% to 9X% (smaller blocks require higher percentage). We also want to make sure there has been enough activity with the freelist to make it worth the time, so we now check that the bytes pushed to the freelist is at least 1/16th of the allocated bytes for those classes. Additionally, we clear batches before destroying them now - this could have prevented some releases to occur (class id 0 rarely releases anyway). Here are the numbers, for about 1M allocations in multiple threads: Size: 16 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 0% released 93% freed -> 0% released 94% freed -> 0% released 95% freed -> 0% released 96% freed -> 0% released 97% freed -> 2% released 98% freed -> 7% released 99% freed -> 27% released Size: 32 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 0% released 93% freed -> 0% released 94% freed -> 0% released 95% freed -> 1% released 96% freed -> 3% released 97% freed -> 7% released 98% freed -> 17% released 99% freed -> 41% released Size: 48 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 0% released 93% freed -> 0% released 94% freed -> 1% released 95% freed -> 3% released 96% freed -> 7% released 97% freed -> 13% released 98% freed -> 27% released 99% freed -> 52% released Size: 64 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 1% released 93% freed -> 2% released 94% freed -> 3% released 95% freed -> 6% released 96% freed -> 11% released 97% freed -> 20% released 98% freed -> 35% released 99% freed -> 59% released Size: 80 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 1% released 91% freed -> 1% released 92% freed -> 2% released 93% freed -> 4% released 94% freed -> 6% released 95% freed -> 10% released 96% freed -> 17% released 97% freed -> 26% released 98% freed -> 41% released 99% freed -> 64% released Size: 96 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 1% released 90% freed -> 1% released 91% freed -> 3% released 92% freed -> 4% released 93% freed -> 6% released 94% freed -> 10% released 95% freed -> 14% released 96% freed -> 21% released 97% freed -> 31% released 98% freed -> 47% released 99% freed -> 68% released Size: 112 85% freed -> 0% released 86% freed -> 1% released 87% freed -> 1% released 88% freed -> 2% released 89% freed -> 3% released 90% freed -> 4% released 91% freed -> 6% released 92% freed -> 8% released 93% freed -> 11% released 94% freed -> 16% released 95% freed -> 22% released 96% freed -> 30% released 97% freed -> 40% released 98% freed -> 55% released 99% freed -> 74% released Size: 128 85% freed -> 0% released 86% freed -> 1% released 87% freed -> 1% released 88% freed -> 2% released 89% freed -> 3% released 90% freed -> 4% released 91% freed -> 6% released 92% freed -> 8% released 93% freed -> 11% released 94% freed -> 16% released 95% freed -> 22% released 96% freed -> 30% released 97% freed -> 40% released 98% freed -> 55% released 99% freed -> 74% released Size: 144 85% freed -> 1% released 86% freed -> 2% released 87% freed -> 3% released 88% freed -> 4% released 89% freed -> 6% released 90% freed -> 7% released 91% freed -> 10% released 92% freed -> 13% released 93% freed -> 17% released 94% freed -> 22% released 95% freed -> 28% released 96% freed -> 37% released 97% freed -> 47% released 98% freed -> 61% released 99% freed -> 78% released Size: 160 85% freed -> 1% released 86% freed -> 2% released 87% freed -> 3% released 88% freed -> 4% released 89% freed -> 5% released 90% freed -> 7% released 91% freed -> 10% released 92% freed -> 13% released 93% freed -> 17% released 94% freed -> 22% released 95% freed -> 28% released 96% freed -> 37% released 97% freed -> 47% released 98% freed -> 61% released 99% freed -> 78% released Size: 176 85% freed -> 2% released 86% freed -> 3% released 87% freed -> 4% released 88% freed -> 6% released 89% freed -> 7% released 90% freed -> 9% released 91% freed -> 12% released 92% freed -> 15% released 93% freed -> 20% released 94% freed -> 25% released 95% freed -> 32% released 96% freed -> 40% released 97% freed -> 51% released 98% freed -> 64% released 99% freed -> 80% released Size: 192 85% freed -> 4% released 86% freed -> 5% released 87% freed -> 6% released 88% freed -> 8% released 89% freed -> 10% released 90% freed -> 13% released 91% freed -> 16% released 92% freed -> 20% released 93% freed -> 24% released 94% freed -> 30% released 95% freed -> 37% released 96% freed -> 45% released 97% freed -> 55% released 98% freed -> 68% released 99% freed -> 82% released Size: 224 85% freed -> 8% released 86% freed -> 10% released 87% freed -> 12% released 88% freed -> 14% released 89% freed -> 17% released 90% freed -> 20% released 91% freed -> 23% released 92% freed -> 28% released 93% freed -> 33% released 94% freed -> 39% released 95% freed -> 46% released 96% freed -> 53% released 97% freed -> 63% released 98% freed -> 73% released 99% freed -> 85% released Size: 240 85% freed -> 8% released 86% freed -> 10% released 87% freed -> 12% released 88% freed -> 14% released 89% freed -> 17% released 90% freed -> 20% released 91% freed -> 23% released 92% freed -> 28% released 93% freed -> 33% released 94% freed -> 39% released 95% freed -> 46% released 96% freed -> 54% released 97% freed -> 63% released 98% freed -> 73% released 99% freed -> 85% released Reviewers: cferris, pcc, hctim, eugenis Subscribers: #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D82031
2020-06-18 01:31:53 +08:00
static uptr getRegionInfoArraySize() { return sizeof(RegionInfoArray); }
uptr getCompactPtrBaseByClassId(uptr ClassId) {
// If we are not compacting pointers, base everything off of 0.
if (sizeof(CompactPtrT) == sizeof(uptr) && CompactPtrScale == 0)
return 0;
return getRegionInfo(ClassId)->RegionBeg;
}
CompactPtrT compactPtr(uptr ClassId, uptr Ptr) {
DCHECK_LE(ClassId, SizeClassMap::LargestClassId);
return compactPtrInternal(getCompactPtrBaseByClassId(ClassId), Ptr);
}
void *decompactPtr(uptr ClassId, CompactPtrT CompactPtr) {
DCHECK_LE(ClassId, SizeClassMap::LargestClassId);
return reinterpret_cast<void *>(
decompactPtrInternal(getCompactPtrBaseByClassId(ClassId), CompactPtr));
}
static BlockInfo findNearestBlock(const char *RegionInfoData, uptr Ptr) {
const RegionInfo *RegionInfoArray =
reinterpret_cast<const RegionInfo *>(RegionInfoData);
uptr ClassId;
uptr MinDistance = -1UL;
for (uptr I = 0; I != NumClasses; ++I) {
if (I == SizeClassMap::BatchClassId)
continue;
uptr Begin = RegionInfoArray[I].RegionBeg;
uptr End = Begin + RegionInfoArray[I].AllocatedUser;
if (Begin > End || End - Begin < SizeClassMap::getSizeByClassId(I))
continue;
uptr RegionDistance;
if (Begin <= Ptr) {
if (Ptr < End)
RegionDistance = 0;
else
RegionDistance = Ptr - End;
} else {
RegionDistance = Begin - Ptr;
}
if (RegionDistance < MinDistance) {
MinDistance = RegionDistance;
ClassId = I;
}
}
BlockInfo B = {};
if (MinDistance <= 8192) {
B.RegionBegin = RegionInfoArray[ClassId].RegionBeg;
B.RegionEnd = B.RegionBegin + RegionInfoArray[ClassId].AllocatedUser;
B.BlockSize = SizeClassMap::getSizeByClassId(ClassId);
B.BlockBegin =
B.RegionBegin + uptr(sptr(Ptr - B.RegionBegin) / sptr(B.BlockSize) *
sptr(B.BlockSize));
while (B.BlockBegin < B.RegionBegin)
B.BlockBegin += B.BlockSize;
while (B.RegionEnd < B.BlockBegin + B.BlockSize)
B.BlockBegin -= B.BlockSize;
}
return B;
}
AtomicOptions Options;
private:
static const uptr RegionSize = 1UL << Config::PrimaryRegionSizeLog;
static const uptr NumClasses = SizeClassMap::NumClasses;
static const uptr PrimarySize = RegionSize * NumClasses;
static const uptr MapSizeIncrement = Config::PrimaryMapSizeIncrement;
// Fill at most this number of batches from the newly map'd memory.
static const u32 MaxNumBatches = SCUDO_ANDROID ? 4U : 8U;
struct RegionStats {
uptr PoppedBlocks;
uptr PushedBlocks;
};
struct ReleaseToOsInfo {
uptr PushedBlocksAtLastRelease;
uptr RangesReleased;
uptr LastReleasedBytes;
u64 LastReleaseAtNs;
};
struct UnpaddedRegionInfo {
HybridMutex Mutex;
SinglyLinkedList<TransferBatch> FreeList;
uptr RegionBeg = 0;
RegionStats Stats = {};
u32 RandState = 0;
uptr MappedUser = 0; // Bytes mapped for user memory.
uptr AllocatedUser = 0; // Bytes allocated for user memory.
MapPlatformData Data = {};
ReleaseToOsInfo ReleaseInfo = {};
bool Exhausted = false;
};
struct RegionInfo : UnpaddedRegionInfo {
char Padding[SCUDO_CACHE_LINE_SIZE -
(sizeof(UnpaddedRegionInfo) % SCUDO_CACHE_LINE_SIZE)] = {};
};
static_assert(sizeof(RegionInfo) % SCUDO_CACHE_LINE_SIZE == 0, "");
uptr PrimaryBase = 0;
MapPlatformData Data = {};
atomic_s32 ReleaseToOsIntervalMs = {};
alignas(SCUDO_CACHE_LINE_SIZE) RegionInfo RegionInfoArray[NumClasses];
RegionInfo *getRegionInfo(uptr ClassId) {
DCHECK_LT(ClassId, NumClasses);
return &RegionInfoArray[ClassId];
}
uptr getRegionBaseByClassId(uptr ClassId) const {
return PrimaryBase + (ClassId << Config::PrimaryRegionSizeLog);
}
static CompactPtrT compactPtrInternal(uptr Base, uptr Ptr) {
return static_cast<CompactPtrT>((Ptr - Base) >> CompactPtrScale);
}
static uptr decompactPtrInternal(uptr Base, CompactPtrT CompactPtr) {
return Base + (static_cast<uptr>(CompactPtr) << CompactPtrScale);
}
NOINLINE TransferBatch *populateFreeList(CacheT *C, uptr ClassId,
RegionInfo *Region) {
const uptr Size = getSizeByClassId(ClassId);
const u32 MaxCount = TransferBatch::getMaxCached(Size);
const uptr RegionBeg = Region->RegionBeg;
const uptr MappedUser = Region->MappedUser;
const uptr TotalUserBytes = Region->AllocatedUser + MaxCount * Size;
// Map more space for blocks, if necessary.
if (TotalUserBytes > MappedUser) {
// Do the mmap for the user memory.
const uptr MapSize =
roundUpTo(TotalUserBytes - MappedUser, MapSizeIncrement);
const uptr RegionBase = RegionBeg - getRegionBaseByClassId(ClassId);
if (UNLIKELY(RegionBase + MappedUser + MapSize > RegionSize)) {
if (!Region->Exhausted) {
Region->Exhausted = true;
ScopedString Str;
getStats(&Str);
Str.append(
"Scudo OOM: The process has exhausted %zuM for size class %zu.\n",
RegionSize >> 20, Size);
Str.output();
}
return nullptr;
}
if (MappedUser == 0)
Region->Data = Data;
if (UNLIKELY(!map(
reinterpret_cast<void *>(RegionBeg + MappedUser), MapSize,
"scudo:primary",
MAP_ALLOWNOMEM | MAP_RESIZABLE |
(useMemoryTagging<Config>(Options.load()) ? MAP_MEMTAG : 0),
&Region->Data)))
return nullptr;
Region->MappedUser += MapSize;
C->getStats().add(StatMapped, MapSize);
}
const u32 NumberOfBlocks = Min(
MaxNumBatches * MaxCount,
static_cast<u32>((Region->MappedUser - Region->AllocatedUser) / Size));
DCHECK_GT(NumberOfBlocks, 0);
constexpr u32 ShuffleArraySize =
MaxNumBatches * TransferBatch::MaxNumCached;
CompactPtrT ShuffleArray[ShuffleArraySize];
DCHECK_LE(NumberOfBlocks, ShuffleArraySize);
const uptr CompactPtrBase = getCompactPtrBaseByClassId(ClassId);
uptr P = RegionBeg + Region->AllocatedUser;
for (u32 I = 0; I < NumberOfBlocks; I++, P += Size)
ShuffleArray[I] = compactPtrInternal(CompactPtrBase, P);
// No need to shuffle the batches size class.
if (ClassId != SizeClassMap::BatchClassId)
shuffle(ShuffleArray, NumberOfBlocks, &Region->RandState);
for (u32 I = 0; I < NumberOfBlocks;) {
TransferBatch *B =
C->createBatch(ClassId, reinterpret_cast<void *>(decompactPtrInternal(
CompactPtrBase, ShuffleArray[I])));
if (UNLIKELY(!B))
return nullptr;
const u32 N = Min(MaxCount, NumberOfBlocks - I);
B->setFromArray(&ShuffleArray[I], N);
Region->FreeList.push_back(B);
I += N;
}
TransferBatch *B = Region->FreeList.front();
Region->FreeList.pop_front();
DCHECK(B);
[scudo][standalone] Optimization pass Summary: This introduces a bunch of small optimizations with the purpose of making the fastpath tighter: - tag more conditions as `LIKELY`/`UNLIKELY`: as a rule of thumb we consider that every operation related to the secondary is unlikely - attempt to reduce the number of potentially extraneous instructions - reorganize the `Chunk` header to not straddle a word boundary and use more appropriate types Note that some `LIKELY`/`UNLIKELY` impact might be less obvious as they are in slow paths (for example in `secondary.cc`), but at this point I am throwing a pretty wide net, and it's consistant and doesn't hurt. This was mosly done for the benfit of Android, but other platforms benefit from it too. An aarch64 Android benchmark gives: - before: ``` BM_youtube/min_time:15.000/repeats:4/manual_time_mean 445244 us 659385 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_median 445007 us 658970 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_stddev 885 us 1332 us 4 ``` - after: ``` BM_youtube/min_time:15.000/repeats:4/manual_time_mean 415697 us 621925 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_median 415913 us 622061 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_stddev 990 us 1163 us 4 ``` Additional since `-Werror=conversion` is enabled on some platforms we are built on, enable it upstream to catch things early: a few sign conversions had slept through and needed additional casting. Reviewers: hctim, morehouse, eugenis, vitalybuka Reviewed By: vitalybuka Subscribers: srhines, mgorny, javed.absar, kristof.beyls, delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D64664 llvm-svn: 366918
2019-07-25 00:36:01 +08:00
DCHECK_GT(B->getCount(), 0);
const uptr AllocatedUser = Size * NumberOfBlocks;
C->getStats().add(StatFree, AllocatedUser);
Region->AllocatedUser += AllocatedUser;
return B;
}
void getStats(ScopedString *Str, uptr ClassId, uptr Rss) {
RegionInfo *Region = getRegionInfo(ClassId);
if (Region->MappedUser == 0)
return;
const uptr InUse = Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks;
[scudo][standalone] Correct releaseToOS behavior Summary: There was an issue in `releaseToOSMaybe`: one of the criteria to decide if we should proceed with the release was wrong. Namely: ``` const uptr N = Sci->Stats.PoppedBlocks - Sci->Stats.PushedBlocks; if (N * BlockSize < PageSize) return; // No chance to release anything. ``` I meant to check if the amount of bytes in the free list was lower than a page, but this actually checks if the amount of **in use** bytes was lower than a page. The correct code is: ``` const uptr BytesInFreeList = Region->AllocatedUser - (Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize; if (BytesInFreeList < PageSize) return 0; // No chance to release anything. ``` Consequences of the bug: - if a class size has less than a page worth of in-use bytes (allocated or in a cache), reclaiming would not occur, whatever the amount of blocks in the free list; in real world scenarios this is unlikely to happen and be impactful; - if a class size had less than a page worth of free bytes (and enough in-use bytes, etc), then reclaiming would be attempted, with likely no result. This means the reclaiming was overzealous at times. I didn't have a good way to test for this, so I changed the prototype of the function to return the number of bytes released, allowing to get the information needed. The test added fails with the initial criteria. Another issue is that `ReleaseToOsInterval` can actually be 0, meaning we always try to release (side note: it's terrible for performances). so change a `> 0` check to `>= 0`. Additionally, decrease the `CanRelease` threshold to `PageSize / 32`. I still have to make that configurable but I will do it at another time. Finally, rename some variables in `printStats`: I feel like "available" was too ambiguous, so change it to "total". Reviewers: morehouse, hctim, eugenis, vitalybuka, cferris Reviewed By: morehouse Subscribers: delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D68471 llvm-svn: 373930
2019-10-08 01:37:39 +08:00
const uptr TotalChunks = Region->AllocatedUser / getSizeByClassId(ClassId);
Str->append("%s %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu "
"inuse: %6zu total: %6zu rss: %6zuK releases: %6zu last "
"released: %6zuK region: 0x%zx (0x%zx)\n",
Region->Exhausted ? "F" : " ", ClassId,
getSizeByClassId(ClassId), Region->MappedUser >> 10,
Region->Stats.PoppedBlocks, Region->Stats.PushedBlocks, InUse,
TotalChunks, Rss >> 10, Region->ReleaseInfo.RangesReleased,
Region->ReleaseInfo.LastReleasedBytes >> 10, Region->RegionBeg,
getRegionBaseByClassId(ClassId));
}
[scudo][standalone] Correct releaseToOS behavior Summary: There was an issue in `releaseToOSMaybe`: one of the criteria to decide if we should proceed with the release was wrong. Namely: ``` const uptr N = Sci->Stats.PoppedBlocks - Sci->Stats.PushedBlocks; if (N * BlockSize < PageSize) return; // No chance to release anything. ``` I meant to check if the amount of bytes in the free list was lower than a page, but this actually checks if the amount of **in use** bytes was lower than a page. The correct code is: ``` const uptr BytesInFreeList = Region->AllocatedUser - (Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize; if (BytesInFreeList < PageSize) return 0; // No chance to release anything. ``` Consequences of the bug: - if a class size has less than a page worth of in-use bytes (allocated or in a cache), reclaiming would not occur, whatever the amount of blocks in the free list; in real world scenarios this is unlikely to happen and be impactful; - if a class size had less than a page worth of free bytes (and enough in-use bytes, etc), then reclaiming would be attempted, with likely no result. This means the reclaiming was overzealous at times. I didn't have a good way to test for this, so I changed the prototype of the function to return the number of bytes released, allowing to get the information needed. The test added fails with the initial criteria. Another issue is that `ReleaseToOsInterval` can actually be 0, meaning we always try to release (side note: it's terrible for performances). so change a `> 0` check to `>= 0`. Additionally, decrease the `CanRelease` threshold to `PageSize / 32`. I still have to make that configurable but I will do it at another time. Finally, rename some variables in `printStats`: I feel like "available" was too ambiguous, so change it to "total". Reviewers: morehouse, hctim, eugenis, vitalybuka, cferris Reviewed By: morehouse Subscribers: delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D68471 llvm-svn: 373930
2019-10-08 01:37:39 +08:00
NOINLINE uptr releaseToOSMaybe(RegionInfo *Region, uptr ClassId,
bool Force = false) {
const uptr BlockSize = getSizeByClassId(ClassId);
const uptr PageSize = getPageSizeCached();
DCHECK_GE(Region->Stats.PoppedBlocks, Region->Stats.PushedBlocks);
[scudo][standalone] Correct releaseToOS behavior Summary: There was an issue in `releaseToOSMaybe`: one of the criteria to decide if we should proceed with the release was wrong. Namely: ``` const uptr N = Sci->Stats.PoppedBlocks - Sci->Stats.PushedBlocks; if (N * BlockSize < PageSize) return; // No chance to release anything. ``` I meant to check if the amount of bytes in the free list was lower than a page, but this actually checks if the amount of **in use** bytes was lower than a page. The correct code is: ``` const uptr BytesInFreeList = Region->AllocatedUser - (Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize; if (BytesInFreeList < PageSize) return 0; // No chance to release anything. ``` Consequences of the bug: - if a class size has less than a page worth of in-use bytes (allocated or in a cache), reclaiming would not occur, whatever the amount of blocks in the free list; in real world scenarios this is unlikely to happen and be impactful; - if a class size had less than a page worth of free bytes (and enough in-use bytes, etc), then reclaiming would be attempted, with likely no result. This means the reclaiming was overzealous at times. I didn't have a good way to test for this, so I changed the prototype of the function to return the number of bytes released, allowing to get the information needed. The test added fails with the initial criteria. Another issue is that `ReleaseToOsInterval` can actually be 0, meaning we always try to release (side note: it's terrible for performances). so change a `> 0` check to `>= 0`. Additionally, decrease the `CanRelease` threshold to `PageSize / 32`. I still have to make that configurable but I will do it at another time. Finally, rename some variables in `printStats`: I feel like "available" was too ambiguous, so change it to "total". Reviewers: morehouse, hctim, eugenis, vitalybuka, cferris Reviewed By: morehouse Subscribers: delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D68471 llvm-svn: 373930
2019-10-08 01:37:39 +08:00
const uptr BytesInFreeList =
Region->AllocatedUser -
(Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize;
if (BytesInFreeList < PageSize)
return 0; // No chance to release anything.
const uptr BytesPushed = (Region->Stats.PushedBlocks -
Region->ReleaseInfo.PushedBlocksAtLastRelease) *
BlockSize;
if (BytesPushed < PageSize)
[scudo][standalone] Correct releaseToOS behavior Summary: There was an issue in `releaseToOSMaybe`: one of the criteria to decide if we should proceed with the release was wrong. Namely: ``` const uptr N = Sci->Stats.PoppedBlocks - Sci->Stats.PushedBlocks; if (N * BlockSize < PageSize) return; // No chance to release anything. ``` I meant to check if the amount of bytes in the free list was lower than a page, but this actually checks if the amount of **in use** bytes was lower than a page. The correct code is: ``` const uptr BytesInFreeList = Region->AllocatedUser - (Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize; if (BytesInFreeList < PageSize) return 0; // No chance to release anything. ``` Consequences of the bug: - if a class size has less than a page worth of in-use bytes (allocated or in a cache), reclaiming would not occur, whatever the amount of blocks in the free list; in real world scenarios this is unlikely to happen and be impactful; - if a class size had less than a page worth of free bytes (and enough in-use bytes, etc), then reclaiming would be attempted, with likely no result. This means the reclaiming was overzealous at times. I didn't have a good way to test for this, so I changed the prototype of the function to return the number of bytes released, allowing to get the information needed. The test added fails with the initial criteria. Another issue is that `ReleaseToOsInterval` can actually be 0, meaning we always try to release (side note: it's terrible for performances). so change a `> 0` check to `>= 0`. Additionally, decrease the `CanRelease` threshold to `PageSize / 32`. I still have to make that configurable but I will do it at another time. Finally, rename some variables in `printStats`: I feel like "available" was too ambiguous, so change it to "total". Reviewers: morehouse, hctim, eugenis, vitalybuka, cferris Reviewed By: morehouse Subscribers: delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D68471 llvm-svn: 373930
2019-10-08 01:37:39 +08:00
return 0; // Nothing new to release.
[scudo][standalone] Release smaller blocks less often Summary: Releasing smaller blocks is costly and only yields significant results when there is a large percentage of free bytes for a given size class (see numbers below). This CL introduces a couple of additional checks for sizes lower than 256. First we want to make sure that there is enough free bytes, relatively to the amount of allocated bytes. We are looking at 8X% to 9X% (smaller blocks require higher percentage). We also want to make sure there has been enough activity with the freelist to make it worth the time, so we now check that the bytes pushed to the freelist is at least 1/16th of the allocated bytes for those classes. Additionally, we clear batches before destroying them now - this could have prevented some releases to occur (class id 0 rarely releases anyway). Here are the numbers, for about 1M allocations in multiple threads: Size: 16 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 0% released 93% freed -> 0% released 94% freed -> 0% released 95% freed -> 0% released 96% freed -> 0% released 97% freed -> 2% released 98% freed -> 7% released 99% freed -> 27% released Size: 32 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 0% released 93% freed -> 0% released 94% freed -> 0% released 95% freed -> 1% released 96% freed -> 3% released 97% freed -> 7% released 98% freed -> 17% released 99% freed -> 41% released Size: 48 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 0% released 93% freed -> 0% released 94% freed -> 1% released 95% freed -> 3% released 96% freed -> 7% released 97% freed -> 13% released 98% freed -> 27% released 99% freed -> 52% released Size: 64 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 1% released 93% freed -> 2% released 94% freed -> 3% released 95% freed -> 6% released 96% freed -> 11% released 97% freed -> 20% released 98% freed -> 35% released 99% freed -> 59% released Size: 80 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 1% released 91% freed -> 1% released 92% freed -> 2% released 93% freed -> 4% released 94% freed -> 6% released 95% freed -> 10% released 96% freed -> 17% released 97% freed -> 26% released 98% freed -> 41% released 99% freed -> 64% released Size: 96 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 1% released 90% freed -> 1% released 91% freed -> 3% released 92% freed -> 4% released 93% freed -> 6% released 94% freed -> 10% released 95% freed -> 14% released 96% freed -> 21% released 97% freed -> 31% released 98% freed -> 47% released 99% freed -> 68% released Size: 112 85% freed -> 0% released 86% freed -> 1% released 87% freed -> 1% released 88% freed -> 2% released 89% freed -> 3% released 90% freed -> 4% released 91% freed -> 6% released 92% freed -> 8% released 93% freed -> 11% released 94% freed -> 16% released 95% freed -> 22% released 96% freed -> 30% released 97% freed -> 40% released 98% freed -> 55% released 99% freed -> 74% released Size: 128 85% freed -> 0% released 86% freed -> 1% released 87% freed -> 1% released 88% freed -> 2% released 89% freed -> 3% released 90% freed -> 4% released 91% freed -> 6% released 92% freed -> 8% released 93% freed -> 11% released 94% freed -> 16% released 95% freed -> 22% released 96% freed -> 30% released 97% freed -> 40% released 98% freed -> 55% released 99% freed -> 74% released Size: 144 85% freed -> 1% released 86% freed -> 2% released 87% freed -> 3% released 88% freed -> 4% released 89% freed -> 6% released 90% freed -> 7% released 91% freed -> 10% released 92% freed -> 13% released 93% freed -> 17% released 94% freed -> 22% released 95% freed -> 28% released 96% freed -> 37% released 97% freed -> 47% released 98% freed -> 61% released 99% freed -> 78% released Size: 160 85% freed -> 1% released 86% freed -> 2% released 87% freed -> 3% released 88% freed -> 4% released 89% freed -> 5% released 90% freed -> 7% released 91% freed -> 10% released 92% freed -> 13% released 93% freed -> 17% released 94% freed -> 22% released 95% freed -> 28% released 96% freed -> 37% released 97% freed -> 47% released 98% freed -> 61% released 99% freed -> 78% released Size: 176 85% freed -> 2% released 86% freed -> 3% released 87% freed -> 4% released 88% freed -> 6% released 89% freed -> 7% released 90% freed -> 9% released 91% freed -> 12% released 92% freed -> 15% released 93% freed -> 20% released 94% freed -> 25% released 95% freed -> 32% released 96% freed -> 40% released 97% freed -> 51% released 98% freed -> 64% released 99% freed -> 80% released Size: 192 85% freed -> 4% released 86% freed -> 5% released 87% freed -> 6% released 88% freed -> 8% released 89% freed -> 10% released 90% freed -> 13% released 91% freed -> 16% released 92% freed -> 20% released 93% freed -> 24% released 94% freed -> 30% released 95% freed -> 37% released 96% freed -> 45% released 97% freed -> 55% released 98% freed -> 68% released 99% freed -> 82% released Size: 224 85% freed -> 8% released 86% freed -> 10% released 87% freed -> 12% released 88% freed -> 14% released 89% freed -> 17% released 90% freed -> 20% released 91% freed -> 23% released 92% freed -> 28% released 93% freed -> 33% released 94% freed -> 39% released 95% freed -> 46% released 96% freed -> 53% released 97% freed -> 63% released 98% freed -> 73% released 99% freed -> 85% released Size: 240 85% freed -> 8% released 86% freed -> 10% released 87% freed -> 12% released 88% freed -> 14% released 89% freed -> 17% released 90% freed -> 20% released 91% freed -> 23% released 92% freed -> 28% released 93% freed -> 33% released 94% freed -> 39% released 95% freed -> 46% released 96% freed -> 54% released 97% freed -> 63% released 98% freed -> 73% released 99% freed -> 85% released Reviewers: cferris, pcc, hctim, eugenis Subscribers: #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D82031
2020-06-18 01:31:53 +08:00
// Releasing smaller blocks is expensive, so we want to make sure that a
// significant amount of bytes are free, and that there has been a good
// amount of batches pushed to the freelist before attempting to release.
if (BlockSize < PageSize / 16U) {
if (!Force && BytesPushed < Region->AllocatedUser / 16U)
return 0;
// We want 8x% to 9x% free bytes (the larger the block, the lower the %).
[scudo][standalone] Release smaller blocks less often Summary: Releasing smaller blocks is costly and only yields significant results when there is a large percentage of free bytes for a given size class (see numbers below). This CL introduces a couple of additional checks for sizes lower than 256. First we want to make sure that there is enough free bytes, relatively to the amount of allocated bytes. We are looking at 8X% to 9X% (smaller blocks require higher percentage). We also want to make sure there has been enough activity with the freelist to make it worth the time, so we now check that the bytes pushed to the freelist is at least 1/16th of the allocated bytes for those classes. Additionally, we clear batches before destroying them now - this could have prevented some releases to occur (class id 0 rarely releases anyway). Here are the numbers, for about 1M allocations in multiple threads: Size: 16 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 0% released 93% freed -> 0% released 94% freed -> 0% released 95% freed -> 0% released 96% freed -> 0% released 97% freed -> 2% released 98% freed -> 7% released 99% freed -> 27% released Size: 32 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 0% released 93% freed -> 0% released 94% freed -> 0% released 95% freed -> 1% released 96% freed -> 3% released 97% freed -> 7% released 98% freed -> 17% released 99% freed -> 41% released Size: 48 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 0% released 93% freed -> 0% released 94% freed -> 1% released 95% freed -> 3% released 96% freed -> 7% released 97% freed -> 13% released 98% freed -> 27% released 99% freed -> 52% released Size: 64 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 0% released 91% freed -> 0% released 92% freed -> 1% released 93% freed -> 2% released 94% freed -> 3% released 95% freed -> 6% released 96% freed -> 11% released 97% freed -> 20% released 98% freed -> 35% released 99% freed -> 59% released Size: 80 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 0% released 90% freed -> 1% released 91% freed -> 1% released 92% freed -> 2% released 93% freed -> 4% released 94% freed -> 6% released 95% freed -> 10% released 96% freed -> 17% released 97% freed -> 26% released 98% freed -> 41% released 99% freed -> 64% released Size: 96 85% freed -> 0% released 86% freed -> 0% released 87% freed -> 0% released 88% freed -> 0% released 89% freed -> 1% released 90% freed -> 1% released 91% freed -> 3% released 92% freed -> 4% released 93% freed -> 6% released 94% freed -> 10% released 95% freed -> 14% released 96% freed -> 21% released 97% freed -> 31% released 98% freed -> 47% released 99% freed -> 68% released Size: 112 85% freed -> 0% released 86% freed -> 1% released 87% freed -> 1% released 88% freed -> 2% released 89% freed -> 3% released 90% freed -> 4% released 91% freed -> 6% released 92% freed -> 8% released 93% freed -> 11% released 94% freed -> 16% released 95% freed -> 22% released 96% freed -> 30% released 97% freed -> 40% released 98% freed -> 55% released 99% freed -> 74% released Size: 128 85% freed -> 0% released 86% freed -> 1% released 87% freed -> 1% released 88% freed -> 2% released 89% freed -> 3% released 90% freed -> 4% released 91% freed -> 6% released 92% freed -> 8% released 93% freed -> 11% released 94% freed -> 16% released 95% freed -> 22% released 96% freed -> 30% released 97% freed -> 40% released 98% freed -> 55% released 99% freed -> 74% released Size: 144 85% freed -> 1% released 86% freed -> 2% released 87% freed -> 3% released 88% freed -> 4% released 89% freed -> 6% released 90% freed -> 7% released 91% freed -> 10% released 92% freed -> 13% released 93% freed -> 17% released 94% freed -> 22% released 95% freed -> 28% released 96% freed -> 37% released 97% freed -> 47% released 98% freed -> 61% released 99% freed -> 78% released Size: 160 85% freed -> 1% released 86% freed -> 2% released 87% freed -> 3% released 88% freed -> 4% released 89% freed -> 5% released 90% freed -> 7% released 91% freed -> 10% released 92% freed -> 13% released 93% freed -> 17% released 94% freed -> 22% released 95% freed -> 28% released 96% freed -> 37% released 97% freed -> 47% released 98% freed -> 61% released 99% freed -> 78% released Size: 176 85% freed -> 2% released 86% freed -> 3% released 87% freed -> 4% released 88% freed -> 6% released 89% freed -> 7% released 90% freed -> 9% released 91% freed -> 12% released 92% freed -> 15% released 93% freed -> 20% released 94% freed -> 25% released 95% freed -> 32% released 96% freed -> 40% released 97% freed -> 51% released 98% freed -> 64% released 99% freed -> 80% released Size: 192 85% freed -> 4% released 86% freed -> 5% released 87% freed -> 6% released 88% freed -> 8% released 89% freed -> 10% released 90% freed -> 13% released 91% freed -> 16% released 92% freed -> 20% released 93% freed -> 24% released 94% freed -> 30% released 95% freed -> 37% released 96% freed -> 45% released 97% freed -> 55% released 98% freed -> 68% released 99% freed -> 82% released Size: 224 85% freed -> 8% released 86% freed -> 10% released 87% freed -> 12% released 88% freed -> 14% released 89% freed -> 17% released 90% freed -> 20% released 91% freed -> 23% released 92% freed -> 28% released 93% freed -> 33% released 94% freed -> 39% released 95% freed -> 46% released 96% freed -> 53% released 97% freed -> 63% released 98% freed -> 73% released 99% freed -> 85% released Size: 240 85% freed -> 8% released 86% freed -> 10% released 87% freed -> 12% released 88% freed -> 14% released 89% freed -> 17% released 90% freed -> 20% released 91% freed -> 23% released 92% freed -> 28% released 93% freed -> 33% released 94% freed -> 39% released 95% freed -> 46% released 96% freed -> 54% released 97% freed -> 63% released 98% freed -> 73% released 99% freed -> 85% released Reviewers: cferris, pcc, hctim, eugenis Subscribers: #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D82031
2020-06-18 01:31:53 +08:00
if ((BytesInFreeList * 100U) / Region->AllocatedUser <
(100U - 1U - BlockSize / 16U))
return 0;
}
if (!Force) {
const s32 IntervalMs = atomic_load_relaxed(&ReleaseToOsIntervalMs);
if (IntervalMs < 0)
[scudo][standalone] Correct releaseToOS behavior Summary: There was an issue in `releaseToOSMaybe`: one of the criteria to decide if we should proceed with the release was wrong. Namely: ``` const uptr N = Sci->Stats.PoppedBlocks - Sci->Stats.PushedBlocks; if (N * BlockSize < PageSize) return; // No chance to release anything. ``` I meant to check if the amount of bytes in the free list was lower than a page, but this actually checks if the amount of **in use** bytes was lower than a page. The correct code is: ``` const uptr BytesInFreeList = Region->AllocatedUser - (Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize; if (BytesInFreeList < PageSize) return 0; // No chance to release anything. ``` Consequences of the bug: - if a class size has less than a page worth of in-use bytes (allocated or in a cache), reclaiming would not occur, whatever the amount of blocks in the free list; in real world scenarios this is unlikely to happen and be impactful; - if a class size had less than a page worth of free bytes (and enough in-use bytes, etc), then reclaiming would be attempted, with likely no result. This means the reclaiming was overzealous at times. I didn't have a good way to test for this, so I changed the prototype of the function to return the number of bytes released, allowing to get the information needed. The test added fails with the initial criteria. Another issue is that `ReleaseToOsInterval` can actually be 0, meaning we always try to release (side note: it's terrible for performances). so change a `> 0` check to `>= 0`. Additionally, decrease the `CanRelease` threshold to `PageSize / 32`. I still have to make that configurable but I will do it at another time. Finally, rename some variables in `printStats`: I feel like "available" was too ambiguous, so change it to "total". Reviewers: morehouse, hctim, eugenis, vitalybuka, cferris Reviewed By: morehouse Subscribers: delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D68471 llvm-svn: 373930
2019-10-08 01:37:39 +08:00
return 0;
[scudo][standalone] Optimization pass Summary: This introduces a bunch of small optimizations with the purpose of making the fastpath tighter: - tag more conditions as `LIKELY`/`UNLIKELY`: as a rule of thumb we consider that every operation related to the secondary is unlikely - attempt to reduce the number of potentially extraneous instructions - reorganize the `Chunk` header to not straddle a word boundary and use more appropriate types Note that some `LIKELY`/`UNLIKELY` impact might be less obvious as they are in slow paths (for example in `secondary.cc`), but at this point I am throwing a pretty wide net, and it's consistant and doesn't hurt. This was mosly done for the benfit of Android, but other platforms benefit from it too. An aarch64 Android benchmark gives: - before: ``` BM_youtube/min_time:15.000/repeats:4/manual_time_mean 445244 us 659385 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_median 445007 us 658970 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_stddev 885 us 1332 us 4 ``` - after: ``` BM_youtube/min_time:15.000/repeats:4/manual_time_mean 415697 us 621925 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_median 415913 us 622061 us 4 BM_youtube/min_time:15.000/repeats:4/manual_time_stddev 990 us 1163 us 4 ``` Additional since `-Werror=conversion` is enabled on some platforms we are built on, enable it upstream to catch things early: a few sign conversions had slept through and needed additional casting. Reviewers: hctim, morehouse, eugenis, vitalybuka Reviewed By: vitalybuka Subscribers: srhines, mgorny, javed.absar, kristof.beyls, delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D64664 llvm-svn: 366918
2019-07-25 00:36:01 +08:00
if (Region->ReleaseInfo.LastReleaseAtNs +
[scudo][standalone] Secondary & general other improvements Summary: This CL changes multiple things to improve performance (notably on Android).We introduce a cache class for the Secondary that is taking care of this mechanism now. The changes: - change the Secondary "freelist" to an array. By keeping free secondary blocks linked together through their headers, we were keeping a page per block, which isn't great. Also we know touch less pages when walking the new "freelist". - fix an issue with the freelist getting full: if the pattern is an ever increasing size malloc then free, the freelist would fill up and entries would not be used. So now we empty the list if we get to many "full" events; - use the global release to os interval option for the secondary: it was too costly to release all the time, particularly for pattern that are malloc(X)/free(X)/malloc(X). Now the release will only occur after the selected interval, when going through the deallocate path; - allow release of the `BatchClassId` class: it is releasable, we just have to make sure we don't mark the batches containing batches pointers as free. - change the default release interval to 1s for Android to match the current Bionic allocator configuration. A patch is coming up to allow changing it through `mallopt`. - lower the smallest class that can be released to `PageSize/64`. Reviewers: cferris, pcc, eugenis, morehouse, hctim Subscribers: phosek, #sanitizers, llvm-commits Tags: #sanitizers, #llvm Differential Revision: https://reviews.llvm.org/D73507
2020-01-28 06:03:21 +08:00
static_cast<u64>(IntervalMs) * 1000000 >
getMonotonicTime()) {
[scudo][standalone] Correct releaseToOS behavior Summary: There was an issue in `releaseToOSMaybe`: one of the criteria to decide if we should proceed with the release was wrong. Namely: ``` const uptr N = Sci->Stats.PoppedBlocks - Sci->Stats.PushedBlocks; if (N * BlockSize < PageSize) return; // No chance to release anything. ``` I meant to check if the amount of bytes in the free list was lower than a page, but this actually checks if the amount of **in use** bytes was lower than a page. The correct code is: ``` const uptr BytesInFreeList = Region->AllocatedUser - (Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize; if (BytesInFreeList < PageSize) return 0; // No chance to release anything. ``` Consequences of the bug: - if a class size has less than a page worth of in-use bytes (allocated or in a cache), reclaiming would not occur, whatever the amount of blocks in the free list; in real world scenarios this is unlikely to happen and be impactful; - if a class size had less than a page worth of free bytes (and enough in-use bytes, etc), then reclaiming would be attempted, with likely no result. This means the reclaiming was overzealous at times. I didn't have a good way to test for this, so I changed the prototype of the function to return the number of bytes released, allowing to get the information needed. The test added fails with the initial criteria. Another issue is that `ReleaseToOsInterval` can actually be 0, meaning we always try to release (side note: it's terrible for performances). so change a `> 0` check to `>= 0`. Additionally, decrease the `CanRelease` threshold to `PageSize / 32`. I still have to make that configurable but I will do it at another time. Finally, rename some variables in `printStats`: I feel like "available" was too ambiguous, so change it to "total". Reviewers: morehouse, hctim, eugenis, vitalybuka, cferris Reviewed By: morehouse Subscribers: delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D68471 llvm-svn: 373930
2019-10-08 01:37:39 +08:00
return 0; // Memory was returned recently.
}
}
ReleaseRecorder Recorder(Region->RegionBeg, &Region->Data);
const uptr CompactPtrBase = getCompactPtrBaseByClassId(ClassId);
auto DecompactPtr = [CompactPtrBase](CompactPtrT CompactPtr) {
return decompactPtrInternal(CompactPtrBase, CompactPtr);
};
auto SkipRegion = [](UNUSED uptr RegionIndex) { return false; };
releaseFreeMemoryToOS(Region->FreeList, Region->AllocatedUser, 1U,
BlockSize, &Recorder, DecompactPtr, SkipRegion);
if (Recorder.getReleasedRangesCount() > 0) {
Region->ReleaseInfo.PushedBlocksAtLastRelease =
Region->Stats.PushedBlocks;
Region->ReleaseInfo.RangesReleased += Recorder.getReleasedRangesCount();
Region->ReleaseInfo.LastReleasedBytes = Recorder.getReleasedBytes();
}
Region->ReleaseInfo.LastReleaseAtNs = getMonotonicTime();
[scudo][standalone] Correct releaseToOS behavior Summary: There was an issue in `releaseToOSMaybe`: one of the criteria to decide if we should proceed with the release was wrong. Namely: ``` const uptr N = Sci->Stats.PoppedBlocks - Sci->Stats.PushedBlocks; if (N * BlockSize < PageSize) return; // No chance to release anything. ``` I meant to check if the amount of bytes in the free list was lower than a page, but this actually checks if the amount of **in use** bytes was lower than a page. The correct code is: ``` const uptr BytesInFreeList = Region->AllocatedUser - (Region->Stats.PoppedBlocks - Region->Stats.PushedBlocks) * BlockSize; if (BytesInFreeList < PageSize) return 0; // No chance to release anything. ``` Consequences of the bug: - if a class size has less than a page worth of in-use bytes (allocated or in a cache), reclaiming would not occur, whatever the amount of blocks in the free list; in real world scenarios this is unlikely to happen and be impactful; - if a class size had less than a page worth of free bytes (and enough in-use bytes, etc), then reclaiming would be attempted, with likely no result. This means the reclaiming was overzealous at times. I didn't have a good way to test for this, so I changed the prototype of the function to return the number of bytes released, allowing to get the information needed. The test added fails with the initial criteria. Another issue is that `ReleaseToOsInterval` can actually be 0, meaning we always try to release (side note: it's terrible for performances). so change a `> 0` check to `>= 0`. Additionally, decrease the `CanRelease` threshold to `PageSize / 32`. I still have to make that configurable but I will do it at another time. Finally, rename some variables in `printStats`: I feel like "available" was too ambiguous, so change it to "total". Reviewers: morehouse, hctim, eugenis, vitalybuka, cferris Reviewed By: morehouse Subscribers: delcypher, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D68471 llvm-svn: 373930
2019-10-08 01:37:39 +08:00
return Recorder.getReleasedBytes();
}
};
} // namespace scudo
#endif // SCUDO_PRIMARY64_H_