Initial commit of snmalloc
History squashed from internal development. Internal history has commit hash: e27a0e485c44a5003a802de2661ce3b21e120316
This commit is contained in:
110
.clang-format
Normal file
110
.clang-format
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignOperands: false
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterExternBlock: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 0
|
||||
ContinuationIndentWidth: 2
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: false
|
||||
ForEachMacros:
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||
Priority: 2
|
||||
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||
Priority: 3
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: AfterHash
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: All
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
TabWidth: 2
|
||||
UseTab: Never
|
||||
...
|
||||
|
||||
333
.gitignore
vendored
333
.gitignore
vendored
@@ -1,330 +1,5 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
build*/
|
||||
CMakeFiles/
|
||||
.vscode/
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
*.sw?
|
||||
|
||||
92
.vsts-ci.yml
Normal file
92
.vsts-ci.yml
Normal file
@@ -0,0 +1,92 @@
|
||||
resources:
|
||||
- repo: self
|
||||
|
||||
phases:
|
||||
- phase: Linux
|
||||
queue:
|
||||
name: 'Hosted Ubuntu 1604'
|
||||
parallel: 2
|
||||
matrix:
|
||||
Debug:
|
||||
BuildType: Debug
|
||||
Release:
|
||||
BuildType: Release
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build libc++-dev libc++abi-dev libc++abi1 libstdc++-7-dev
|
||||
# sudo apt-get install clang-6.0 clang++-6.0
|
||||
|
||||
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-6.0 100
|
||||
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100
|
||||
sudo update-alternatives --set cc /usr/bin/clang
|
||||
|
||||
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-6.0 100
|
||||
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
|
||||
sudo update-alternatives --set c++ /usr/bin/clang++
|
||||
|
||||
displayName: 'Install Build Dependencies'
|
||||
|
||||
- task: CMake@1
|
||||
displayName: 'CMake .. -GNinja -DCMAKE_BUILD_TYPE=$(BuildType) -DCMAKE_CXX_FLAGS="-stdlib=libstdc++ -std=c++17"'
|
||||
inputs:
|
||||
cmakeArgs: '.. -GNinja -DCMAKE_BUILD_TYPE=$(BuildType) -DCMAKE_CXX_FLAGS="-stdlib=libstdc++ -std=c++17"'
|
||||
|
||||
- script: |
|
||||
ninja
|
||||
ctest -j 4 --output-on-failure
|
||||
|
||||
workingDirectory: build
|
||||
failOnStderr: true
|
||||
displayName: 'Compile & Test'
|
||||
|
||||
- script: |
|
||||
sudo cp libsnmallocshim.so /usr/local/lib/
|
||||
ninja clean
|
||||
LD_PRELOAD=/usr/local/lib/libsnmallocshim.so ninja
|
||||
workingDirectory: build
|
||||
failOnStderr: true
|
||||
displayName: 'LD_PRELOAD Compile'
|
||||
|
||||
- phase: Windows
|
||||
queue:
|
||||
name: 'Hosted VS2017'
|
||||
parallel: 2
|
||||
matrix:
|
||||
Debug:
|
||||
BuildType: Debug
|
||||
Release:
|
||||
BuildType: Release
|
||||
|
||||
steps:
|
||||
- task: CMake@1
|
||||
displayName: 'CMake .. -G"Visual Studio 15 2017 Win64"'
|
||||
inputs:
|
||||
cmakeArgs: '.. -G"Visual Studio 15 2017 Win64"'
|
||||
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build solution build/snmalloc.sln'
|
||||
inputs:
|
||||
solution: build/snmalloc.sln
|
||||
msbuildArguments: '/m /p:Configuration=$(BuildType)'
|
||||
|
||||
- script: 'ctest -j 4 --interactive-debug-mode 0 --output-on-failure'
|
||||
workingDirectory: build
|
||||
displayName: 'Run Ctest'
|
||||
|
||||
- phase: Format
|
||||
queue:
|
||||
name: 'Hosted Ubuntu 1604'
|
||||
steps:
|
||||
- script: |
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main"
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-format-6.0
|
||||
sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-6.0 100
|
||||
|
||||
bash check-format.sh
|
||||
|
||||
displayName: 'Check Format'
|
||||
107
CMakeLists.txt
Normal file
107
CMakeLists.txt
Normal file
@@ -0,0 +1,107 @@
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
project(snmalloc C CXX)
|
||||
|
||||
option(USE_SNMALLOC_STATS "Track allocation stats" OFF)
|
||||
option(USE_MEASURE "Measure performance with histograms" OFF)
|
||||
option(USE_SBRK "Use sbrk instead of mmap" OFF)
|
||||
|
||||
macro(subdirlist result curdir)
|
||||
file(GLOB children LIST_DIRECTORIES true RELATIVE ${curdir} ${curdir}/*)
|
||||
set(dirlist "")
|
||||
foreach(child ${children})
|
||||
if(IS_DIRECTORY ${curdir}/${child})
|
||||
list(APPEND dirlist ${child})
|
||||
endif()
|
||||
endforeach()
|
||||
set(${result} ${dirlist})
|
||||
endmacro()
|
||||
|
||||
macro(linklibs project)
|
||||
if(NOT MSVC)
|
||||
target_link_libraries(${project} ${CMAKE_THREAD_LIBS_INIT})
|
||||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||
target_link_libraries(${project} atomic)
|
||||
endif()
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
if(MSVC)
|
||||
add_compile_options(/WX /W4 /wd4127 /wd4324 /wd4201 /std:c++latest)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG")
|
||||
else()
|
||||
find_package(Threads REQUIRED)
|
||||
add_compile_options(-mcx16 -march=native -Wall -Wextra -Werror -g)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
if(USE_SNMALLOC_STATS)
|
||||
add_definitions(-DUSE_SNMALLOC_STATS)
|
||||
endif()
|
||||
|
||||
if(USE_MEASURE)
|
||||
add_definitions(-DUSE_MEASURE)
|
||||
endif()
|
||||
|
||||
if(USE_SBRK)
|
||||
add_definitions(-DUSE_SBRK)
|
||||
endif()
|
||||
|
||||
if(NOT MSVC)
|
||||
add_library(snmallocshim SHARED src/override/malloc.cc)
|
||||
target_link_libraries(snmallocshim -pthread)
|
||||
target_include_directories(snmallocshim PRIVATE src)
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
|
||||
set(TESTDIR ${CMAKE_CURRENT_SOURCE_DIR}/src/test)
|
||||
subdirlist(TEST_CATEGORIES ${TESTDIR})
|
||||
foreach(TEST_CATEGORY ${TEST_CATEGORIES})
|
||||
subdirlist(TESTS ${TESTDIR}/${TEST_CATEGORY})
|
||||
foreach(TEST ${TESTS})
|
||||
unset(SRC)
|
||||
aux_source_directory(${TESTDIR}/${TEST_CATEGORY}/${TEST} SRC)
|
||||
set(TESTNAME "${TEST_CATEGORY}-${TEST}")
|
||||
add_executable(${TESTNAME} ${SRC} src/override/new.cc)
|
||||
target_include_directories(${TESTNAME} PRIVATE src)
|
||||
linklibs(${TESTNAME})
|
||||
add_test(${TESTNAME} ${TESTNAME})
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
|
||||
# The clang-format tool is installed under a variety of different names. Try
|
||||
# to find a sensible one. Only look for 6.0 and 7.0 versions explicitly - we
|
||||
# don't know whether our clang-format file will work with newer versions of the
|
||||
# tool
|
||||
set(CLANG_FORMAT_NAMES
|
||||
clang-format-7.0
|
||||
clang-format-6.0
|
||||
clang-format70
|
||||
clang-format60
|
||||
clang-format)
|
||||
|
||||
# Loop over each of the possible names of clang-format and try to find one.
|
||||
set(CLANG_FORMAT CLANG_FORMAT-NOTFOUND)
|
||||
foreach (NAME IN ITEMS ${CLANG_FORMAT_NAMES})
|
||||
if (${CLANG_FORMAT} STREQUAL "CLANG_FORMAT-NOTFOUND")
|
||||
find_program(CLANG_FORMAT ${NAME})
|
||||
endif ()
|
||||
endforeach()
|
||||
|
||||
# If we've found a clang-format tool, generate a target for it, otherwise emit
|
||||
# a warning.
|
||||
if (${CLANG_FORMAT} STREQUAL "CLANG_FORMAT-NOTFOUND")
|
||||
message(WARNING "Not generating clangformat target, no clang-format tool found")
|
||||
else ()
|
||||
message(STATUS "Generating clangformat target using ${CLANG_FORMAT}")
|
||||
file(GLOB_RECURSE ALL_SOURCE_FILES *.cc *.h *.hh)
|
||||
add_custom_target(
|
||||
clangformat
|
||||
COMMAND ${CLANG_FORMAT}
|
||||
-i
|
||||
${ALL_SOURCE_FILES}
|
||||
)
|
||||
endif()
|
||||
78
README.md
78
README.md
@@ -1,3 +1,81 @@
|
||||
# snmalloc
|
||||
|
||||
snmalloc is a research allocator. Its key design features are:
|
||||
|
||||
* Memory that is freed by the same thread that allocated it does not require any
|
||||
synchronising operations.
|
||||
* Freeing memory in a different thread to initially allocated it, does not take
|
||||
any locks and instead uses a novel message passing scheme to return the
|
||||
memory to the original allocator, where it is recycled.
|
||||
* The allocator uses large ranges of pages to reduce the amount of meta-data
|
||||
required.
|
||||
|
||||
# Building on Windows
|
||||
|
||||
The Windows build currently depends on Visual Studio 2017.
|
||||
To build with Visual Studio:
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G "Visual Studio 15 2017 Win64" ..
|
||||
cmake --build . --config Debug
|
||||
cmake --build . --config Release
|
||||
cmake --build . --config RelWithDebInfo
|
||||
```
|
||||
|
||||
You can also omit the last three steps and build from the IDE.
|
||||
Visual Studio builds use a separate directory to keep the binaries for each
|
||||
build configuration.
|
||||
|
||||
Alternatively, you can follow the steps in the next section to build with Ninja
|
||||
using the Visual Studio compiler.
|
||||
|
||||
# Building on Linux or FreeBSD
|
||||
Snmalloc has very few dependencies, CMake, Ninja, Clang 6.0 or later and a C++17
|
||||
standard library.
|
||||
Building with GCC is currently not supported because GCC lacks support for the
|
||||
`selectany` attribute to specify variables in a COMDAT.
|
||||
|
||||
To build a debug configuration:
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G Ninja .. -DCMAKE_BUILD_TYPE=Debug
|
||||
ninja
|
||||
```
|
||||
To build a release configuration:
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G Ninja .. -DCMAKE_BUILD_TYPE=Release
|
||||
ninja
|
||||
```
|
||||
To build with optimizations on, but with debug information:
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G Ninja .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
ninja
|
||||
```
|
||||
|
||||
The build produces a binary `libsnmallocshim.so`. This file can be
|
||||
`LD_PRELOAD`ed to use the allocator in place of the system allocator, for
|
||||
example, you can run the build script using the snmalloc as the allocator for
|
||||
your toolchain:
|
||||
|
||||
```
|
||||
LD_PRELOAD=/usr/local/lib/libsnmallocshim.so ninja
|
||||
```
|
||||
|
||||
# CMake Feature Flags
|
||||
|
||||
These can be added to your cmake command line.
|
||||
|
||||
```
|
||||
-DUSE_SNMALLOC_STATS=ON // Track allocation stats
|
||||
-DUSE_MEASURE=ON // Measure performance with histograms
|
||||
```
|
||||
|
||||
# Contributing
|
||||
|
||||
|
||||
18
check-format.sh
Normal file
18
check-format.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
set -u
|
||||
|
||||
unformatted_files=""
|
||||
for f in `find . -name *.h -o -name *.hh -o -name *.cc`; do
|
||||
d=`clang-format -style=file $f | diff $f -`
|
||||
if [ "$d" != "" ]; then
|
||||
if [ "$unformatted_files" != "" ]; then
|
||||
unformatted_files+=$'\n'
|
||||
fi
|
||||
unformatted_files+="$f"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$unformatted_files" != "" ]; then
|
||||
echo "$unformatted_files"
|
||||
exit 1
|
||||
fi
|
||||
113
src/ds/aba.h
Normal file
113
src/ds/aba.h
Normal file
@@ -0,0 +1,113 @@
|
||||
#pragma once
|
||||
|
||||
#include "bits.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<typename T, Construction c = RequiresInit>
|
||||
class ABA
|
||||
{
|
||||
public:
|
||||
#ifdef PLATFORM_IS_X86
|
||||
struct alignas(2 * sizeof(std::size_t)) Linked
|
||||
{
|
||||
T* ptr;
|
||||
uintptr_t aba;
|
||||
};
|
||||
|
||||
struct Independent
|
||||
{
|
||||
std::atomic<T*> ptr;
|
||||
std::atomic<uintptr_t> aba;
|
||||
};
|
||||
|
||||
static_assert(
|
||||
sizeof(Linked) == sizeof(Independent),
|
||||
"Expecting identical struct sizes in union");
|
||||
static_assert(
|
||||
sizeof(Linked) == (2 * sizeof(std::size_t)),
|
||||
"Expecting ABA to be the size of two pointers");
|
||||
|
||||
using Cmp = Linked;
|
||||
#else
|
||||
using Cmp = T*;
|
||||
#endif
|
||||
|
||||
private:
|
||||
#ifdef PLATFORM_IS_X86
|
||||
union
|
||||
{
|
||||
alignas(2 * sizeof(std::size_t)) std::atomic<Linked> linked;
|
||||
Independent independent;
|
||||
};
|
||||
#else
|
||||
std::atomic<T*> ptr;
|
||||
#endif
|
||||
|
||||
public:
|
||||
ABA()
|
||||
{
|
||||
if constexpr (c == RequiresInit)
|
||||
init(nullptr);
|
||||
}
|
||||
|
||||
void init(T* x)
|
||||
{
|
||||
#ifdef PLATFORM_IS_X86
|
||||
independent.ptr.store(x, std::memory_order_relaxed);
|
||||
independent.aba.store(0, std::memory_order_relaxed);
|
||||
#else
|
||||
ptr.store(x, std::memory_order_relaxed);
|
||||
#endif
|
||||
}
|
||||
|
||||
T* peek()
|
||||
{
|
||||
return independent.ptr.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
Cmp read()
|
||||
{
|
||||
return
|
||||
#ifdef PLATFORM_IS_X86
|
||||
Cmp{independent.ptr.load(std::memory_order_relaxed),
|
||||
independent.aba.load(std::memory_order_relaxed)};
|
||||
#else
|
||||
ptr.load(std::memory_order_relaxed);
|
||||
#endif
|
||||
}
|
||||
|
||||
static T* load(Cmp& from)
|
||||
{
|
||||
#ifdef PLATFORM_IS_X86
|
||||
return from.ptr;
|
||||
#else
|
||||
return from;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool compare_exchange(Cmp& expect, T* value)
|
||||
{
|
||||
#ifdef PLATFORM_IS_X86
|
||||
# if defined(_MSC_VER) && defined(PLATFORM_BITS_64)
|
||||
return _InterlockedCompareExchange128(
|
||||
(volatile __int64*)&linked,
|
||||
expect.aba + 1,
|
||||
(__int64)value,
|
||||
(__int64*)&expect);
|
||||
# else
|
||||
# if defined(__GNUC__) && !defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16)
|
||||
#error You must compile with -mcx16 to enable 16-bit atomic compare and swap.
|
||||
# endif
|
||||
Cmp xchg{value, expect.aba + 1};
|
||||
|
||||
return linked.compare_exchange_weak(
|
||||
expect, xchg, std::memory_order_relaxed, std::memory_order_relaxed);
|
||||
# endif
|
||||
#else
|
||||
return ptr.compare_exchange_weak(
|
||||
expect, value, std::memory_order_relaxed, std::memory_order_relaxed);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
}
|
||||
466
src/ds/bits.h
Normal file
466
src/ds/bits.h
Normal file
@@ -0,0 +1,466 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# include <immintrin.h>
|
||||
# include <intrin.h>
|
||||
# define ALWAYSINLINE __forceinline
|
||||
# define NOINLINE __declspec(noinline)
|
||||
# define HEADER_GLOBAL __declspec(selectany)
|
||||
#else
|
||||
# include <cpuid.h>
|
||||
# include <emmintrin.h>
|
||||
# define ALWAYSINLINE __attribute__((always_inline))
|
||||
# define NOINLINE __attribute__((noinline))
|
||||
# define HEADER_GLOBAL __attribute__((selectany))
|
||||
#endif
|
||||
|
||||
#if defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \
|
||||
defined(__amd64__) || defined(__x86_64__) || defined(_M_X64) || \
|
||||
defined(_M_AMD64)
|
||||
# define PLATFORM_IS_X86
|
||||
# if defined(__linux__) && !defined(OPEN_ENCLAVE)
|
||||
# include <x86intrin.h>
|
||||
# endif
|
||||
# if defined(__amd64__) || defined(__x86_64__) || defined(_M_X64) || \
|
||||
defined(_M_AMD64)
|
||||
# define PLATFORM_BITS_64
|
||||
# else
|
||||
# define PLATFORM_BITS_32
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && defined(PLATFORM_BITS_32)
|
||||
# include <intsafe.h>
|
||||
#endif
|
||||
|
||||
#ifndef __has_builtin
|
||||
# define __has_builtin(x) 0
|
||||
#endif
|
||||
|
||||
#define UNUSED(x) ((void)x)
|
||||
|
||||
// #define USE_LZCNT
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef pause
|
||||
# undef pause
|
||||
#endif
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
// Used to enable trivial constructors for
|
||||
// class that zero init is sufficient.
|
||||
// Supplying PreZeroed means the memory is pre-zeroed i.e. a global section
|
||||
// RequiresInit is if the class needs to zero its fields.
|
||||
enum Construction
|
||||
{
|
||||
PreZeroed,
|
||||
RequiresInit
|
||||
};
|
||||
|
||||
namespace bits
|
||||
{
|
||||
static constexpr size_t BITS = sizeof(size_t) * 8;
|
||||
|
||||
static constexpr bool is64()
|
||||
{
|
||||
return BITS == 64;
|
||||
}
|
||||
|
||||
static constexpr size_t ADDRESS_BITS = is64() ? 48 : 32;
|
||||
|
||||
inline void pause()
|
||||
{
|
||||
#if defined(PLATFORM_IS_X86)
|
||||
_mm_pause();
|
||||
#else
|
||||
# warning "Missing pause intrinsic"
|
||||
#endif
|
||||
}
|
||||
|
||||
inline uint64_t tick()
|
||||
{
|
||||
#if defined(PLATFORM_IS_X86)
|
||||
# if defined(_MSC_VER)
|
||||
return __rdtsc();
|
||||
# elif defined(__clang__)
|
||||
return __builtin_readcyclecounter();
|
||||
# else
|
||||
return __builtin_ia32_rdtsc();
|
||||
# endif
|
||||
#else
|
||||
# error Define CPU tick for this platform
|
||||
#endif
|
||||
}
|
||||
|
||||
inline uint64_t tickp()
|
||||
{
|
||||
#if defined(PLATFORM_IS_X86)
|
||||
# if defined(_MSC_VER)
|
||||
unsigned int aux;
|
||||
return __rdtscp(&aux);
|
||||
# else
|
||||
unsigned aux;
|
||||
return __builtin_ia32_rdtscp(&aux);
|
||||
# endif
|
||||
#else
|
||||
# error Define CPU tick for this platform
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void halt_out_of_order()
|
||||
{
|
||||
#if defined(PLATFORM_IS_X86)
|
||||
# if defined(_MSC_VER)
|
||||
int cpu_info[4];
|
||||
__cpuid(cpu_info, 0);
|
||||
# else
|
||||
unsigned int eax, ebx, ecx, edx;
|
||||
__get_cpuid(0, &eax, &ebx, &ecx, &edx);
|
||||
# endif
|
||||
#else
|
||||
# error Define CPU benchmark start time for this platform
|
||||
#endif
|
||||
}
|
||||
|
||||
inline uint64_t benchmark_time_start()
|
||||
{
|
||||
halt_out_of_order();
|
||||
return tick();
|
||||
}
|
||||
|
||||
inline uint64_t benchmark_time_end()
|
||||
{
|
||||
uint64_t t = tickp();
|
||||
halt_out_of_order();
|
||||
return t;
|
||||
}
|
||||
|
||||
inline size_t clz(size_t x)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
# ifdef USE_LZCNT
|
||||
# ifdef PLATFORM_BITS_64
|
||||
return __lzcnt64(x);
|
||||
# else
|
||||
return __lzcnt((uint32_t)x);
|
||||
# endif
|
||||
# else
|
||||
unsigned long index;
|
||||
|
||||
# ifdef PLATFORM_BITS_64
|
||||
_BitScanReverse64(&index, x);
|
||||
# else
|
||||
_BitScanReverse(&index, (unsigned long)x);
|
||||
# endif
|
||||
|
||||
return BITS - index - 1;
|
||||
# endif
|
||||
#else
|
||||
return (size_t)__builtin_clzl(x);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline constexpr size_t rotr_const(size_t x, size_t n)
|
||||
{
|
||||
size_t nn = n & (BITS - 1);
|
||||
return (x >> nn) | (x << (((size_t) - (int)nn) & (BITS - 1)));
|
||||
}
|
||||
|
||||
inline constexpr size_t rotl_const(size_t x, size_t n)
|
||||
{
|
||||
size_t nn = n & (BITS - 1);
|
||||
return (x << nn) | (x >> (((size_t) - (int)nn) & (BITS - 1)));
|
||||
}
|
||||
|
||||
inline size_t rotr(size_t x, size_t n)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
# ifdef PLATFORM_BITS_64
|
||||
return _rotr64(x, (int)n);
|
||||
# else
|
||||
return _rotr((uint32_t)x, (int)n);
|
||||
# endif
|
||||
#else
|
||||
return rotr_const(x, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline size_t rotl(size_t x, size_t n)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
# ifdef PLATFORM_BITS_64
|
||||
return _rotl64(x, (int)n);
|
||||
# else
|
||||
return _rotl((uint32_t)x, (int)n);
|
||||
# endif
|
||||
#else
|
||||
return rotl_const(x, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr size_t clz_const(size_t x)
|
||||
{
|
||||
size_t n = 0;
|
||||
|
||||
for (int i = BITS - 1; i >= 0; i--)
|
||||
{
|
||||
size_t mask = (size_t)1 << i;
|
||||
|
||||
if ((x & mask) == mask)
|
||||
return n;
|
||||
|
||||
n++;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
inline size_t ctz(size_t x)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
# ifdef PLATFORM_BITS_64
|
||||
return _tzcnt_u64(x);
|
||||
# else
|
||||
return _tzcnt_u32((uint32_t)x);
|
||||
# endif
|
||||
#else
|
||||
return (size_t)__builtin_ctzl(x);
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr size_t ctz_const(size_t x)
|
||||
{
|
||||
size_t n = 0;
|
||||
|
||||
for (size_t i = 0; i < BITS; i++)
|
||||
{
|
||||
size_t mask = (size_t)1 << i;
|
||||
|
||||
if ((x & mask) == mask)
|
||||
return n;
|
||||
|
||||
n++;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
inline size_t umul(size_t x, size_t y, bool& overflow)
|
||||
{
|
||||
#if __has_builtin(__builtin_mul_overflow)
|
||||
size_t prod;
|
||||
overflow = __builtin_mul_overflow(x, y, &prod);
|
||||
return prod;
|
||||
#elif defined(_MSC_VER)
|
||||
# if defined(PLATFORM_BITS_64)
|
||||
size_t high_prod;
|
||||
size_t prod = _umul128(x, y, &high_prod);
|
||||
overflow = high_prod != 0;
|
||||
return prod;
|
||||
# else
|
||||
size_t prod;
|
||||
overflow = S_OK == UIntMult(x, y, &prod);
|
||||
return prod;
|
||||
# endif
|
||||
#else
|
||||
size_t prod = x * y;
|
||||
return y && (x > ((size_t)-1 / y));
|
||||
#endif
|
||||
}
|
||||
|
||||
inline size_t next_pow2(size_t x)
|
||||
{
|
||||
// Correct for numbers [0..MAX_SIZE >> 1).
|
||||
// Returns 1 for x > (MAX_SIZE >> 1).
|
||||
if (x <= 2)
|
||||
return x;
|
||||
|
||||
return (size_t)1 << (BITS - clz(x - 1));
|
||||
}
|
||||
|
||||
inline size_t next_pow2_bits(size_t x)
|
||||
{
|
||||
// Correct for numbers [1..MAX_SIZE].
|
||||
// Returns 64 for 0. Approximately 2 cycles.
|
||||
return BITS - clz(x - 1);
|
||||
}
|
||||
|
||||
constexpr size_t next_pow2_const(size_t x)
|
||||
{
|
||||
if (x <= 2)
|
||||
return x;
|
||||
|
||||
return (size_t)1 << (BITS - clz_const(x - 1));
|
||||
}
|
||||
|
||||
constexpr size_t next_pow2_bits_const(size_t x)
|
||||
{
|
||||
return BITS - clz_const(x - 1);
|
||||
}
|
||||
|
||||
inline static size_t hash(void* p)
|
||||
{
|
||||
size_t x = (size_t)p;
|
||||
|
||||
if (is64())
|
||||
{
|
||||
x = ~x + (x << 21);
|
||||
x = x ^ (x >> 24);
|
||||
x = (x + (x << 3)) + (x << 8);
|
||||
x = x ^ (x >> 14);
|
||||
x = (x + (x << 2)) + (x << 4);
|
||||
x = x ^ (x >> 28);
|
||||
x = x + (x << 31);
|
||||
}
|
||||
else
|
||||
{
|
||||
x = ~x + (x << 15);
|
||||
x = x ^ (x >> 12);
|
||||
x = x + (x << 2);
|
||||
x = x ^ (x >> 4);
|
||||
x = (x + (x << 3)) + (x << 11);
|
||||
x = x ^ (x >> 16);
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline size_t align_down(size_t value, size_t alignment)
|
||||
{
|
||||
assert(next_pow2(alignment) == alignment);
|
||||
|
||||
size_t align_1 = alignment - 1;
|
||||
value &= ~align_1;
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline size_t align_up(size_t value, size_t alignment)
|
||||
{
|
||||
assert(next_pow2(alignment) == alignment);
|
||||
|
||||
size_t align_1 = alignment - 1;
|
||||
value += align_1;
|
||||
value &= ~align_1;
|
||||
return value;
|
||||
}
|
||||
|
||||
template<size_t alignment>
|
||||
static inline bool is_aligned_block(void* p, size_t size)
|
||||
{
|
||||
assert(next_pow2(alignment) == alignment);
|
||||
|
||||
return (((size_t)p | size) & (alignment - 1)) == 0;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
constexpr T inc_mod(T v, T mod)
|
||||
{
|
||||
static_assert(
|
||||
std::is_integral<T>::value, "inc_mod can only be used on integers");
|
||||
|
||||
using S = std::make_signed_t<T>;
|
||||
constexpr S shift = (sizeof(S) * 8) - 1;
|
||||
|
||||
S a = (S)(v + 1);
|
||||
S b = (S)(mod - a - 1);
|
||||
return a & ~(b >> shift);
|
||||
}
|
||||
|
||||
/************************************************
|
||||
*
|
||||
* Map large range of strictly positive integers
|
||||
* into an exponent and mantissa pair.
|
||||
*
|
||||
* The reverse mapping is given as:
|
||||
*
|
||||
* e | m | value
|
||||
* ---------------------------------
|
||||
* 0 | x1 ... xm | 0..00 x1 .. xm
|
||||
* 1 | x1 ... xm | 0..01 x1 .. xm
|
||||
* 2 | x1 ... xm | 0..1 x1 .. xm 0
|
||||
* 3 | x1 ... xm | 0.1 x1 .. xm 00
|
||||
*
|
||||
* The forward mapping maps a value to the
|
||||
* smallest exponent and mantissa with a
|
||||
* reverse mapping not less than the value.
|
||||
*
|
||||
* Does not work for value=0.
|
||||
***********************************************/
|
||||
template<size_t MANTISSA_BITS, size_t LOW_BITS = 0>
|
||||
static size_t to_exp_mant(size_t value)
|
||||
{
|
||||
value += ((size_t)1 << (LOW_BITS)) - 1;
|
||||
value >>= LOW_BITS;
|
||||
|
||||
if (MANTISSA_BITS > 0)
|
||||
{
|
||||
size_t LEADING_BIT = ((size_t)1 << MANTISSA_BITS) >> 1;
|
||||
size_t MANTISSA_MASK = ((size_t)1 << MANTISSA_BITS) - 1;
|
||||
|
||||
value = value - 1;
|
||||
|
||||
size_t e = (bits::BITS - clz(value | LEADING_BIT)) - MANTISSA_BITS;
|
||||
size_t shift_e = (e == 0) ? 0 : e - 1;
|
||||
size_t m = (value >> shift_e) & MANTISSA_MASK;
|
||||
|
||||
return (e << MANTISSA_BITS) + m;
|
||||
}
|
||||
else
|
||||
{
|
||||
return bits::next_pow2_bits(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t MANTISSA_BITS, size_t LOW_BITS = 0>
|
||||
constexpr static size_t to_exp_mant_const(size_t value)
|
||||
{
|
||||
value += ((size_t)1 << LOW_BITS) - 1;
|
||||
value >>= LOW_BITS;
|
||||
|
||||
if (MANTISSA_BITS > 0)
|
||||
{
|
||||
size_t LEADING_BIT = (size_t)1 << (MANTISSA_BITS - 1);
|
||||
size_t MANTISSA_MASK = ((size_t)1 << MANTISSA_BITS) - 1;
|
||||
|
||||
value = value - 1;
|
||||
|
||||
size_t e =
|
||||
(bits::BITS - clz_const(value | LEADING_BIT)) - MANTISSA_BITS;
|
||||
size_t shift_e = (e == 0) ? 0 : e - 1;
|
||||
size_t m = (value >> shift_e) & MANTISSA_MASK;
|
||||
|
||||
return (e << MANTISSA_BITS) + m;
|
||||
}
|
||||
else
|
||||
{
|
||||
return bits::next_pow2_bits_const(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t MANTISSA_BITS, size_t LOW_BITS = 0>
|
||||
constexpr static size_t from_exp_mant(size_t m_e)
|
||||
{
|
||||
if (MANTISSA_BITS > 0)
|
||||
{
|
||||
size_t MANTISSA_MASK = ((size_t)1 << MANTISSA_BITS) - 1;
|
||||
size_t m = m_e & MANTISSA_MASK;
|
||||
size_t e = m_e >> MANTISSA_BITS;
|
||||
size_t b = e == 0 ? 0 : 1;
|
||||
size_t shifted_e = e - b;
|
||||
size_t extended_m = (m + ((size_t)b << MANTISSA_BITS)) + 1;
|
||||
return extended_m << (shifted_e + LOW_BITS);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (size_t)1 << (m_e + LOW_BITS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/ds/csv.h
Normal file
55
src/ds/csv.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class CSVStream
|
||||
{
|
||||
private:
|
||||
std::ostream* out;
|
||||
bool first = true;
|
||||
|
||||
public:
|
||||
class Endl
|
||||
{};
|
||||
|
||||
Endl endl;
|
||||
|
||||
CSVStream(std::ostream* o) : out(o) {}
|
||||
|
||||
void preprint()
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
*out << ", ";
|
||||
}
|
||||
else
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
CSVStream& operator<<(const std::string& str)
|
||||
{
|
||||
preprint();
|
||||
*out << str;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CSVStream& operator<<(uint64_t u)
|
||||
{
|
||||
preprint();
|
||||
*out << u;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CSVStream& operator<<(Endl)
|
||||
{
|
||||
*out << std::endl;
|
||||
first = true;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
}
|
||||
120
src/ds/dllist.h
Normal file
120
src/ds/dllist.h
Normal file
@@ -0,0 +1,120 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<class T, uintptr_t terminator = 0>
|
||||
class DLList
|
||||
{
|
||||
private:
|
||||
static_assert(
|
||||
std::is_same<decltype(((T*)0)->prev), T*>::value, "T->prev must be a T*");
|
||||
static_assert(
|
||||
std::is_same<decltype(((T*)0)->next), T*>::value, "T->next must be a T*");
|
||||
|
||||
T* head = (T*)terminator;
|
||||
|
||||
public:
|
||||
T* get_head()
|
||||
{
|
||||
return head;
|
||||
}
|
||||
|
||||
T* pop()
|
||||
{
|
||||
T* item = head;
|
||||
|
||||
if (item != (T*)terminator)
|
||||
remove(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void insert(T* item)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
debug_check_not_contains(item);
|
||||
#endif
|
||||
|
||||
item->next = head;
|
||||
item->prev = (T*)terminator;
|
||||
|
||||
if (head != (T*)terminator)
|
||||
head->prev = item;
|
||||
|
||||
head = item;
|
||||
#ifndef NDEBUG
|
||||
debug_check();
|
||||
#endif
|
||||
}
|
||||
|
||||
void remove(T* item)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
debug_check_contains(item);
|
||||
#endif
|
||||
|
||||
if (item->next != (T*)terminator)
|
||||
item->next->prev = item->prev;
|
||||
|
||||
if (item->prev != (T*)terminator)
|
||||
item->prev->next = item->next;
|
||||
else
|
||||
head = item->next;
|
||||
|
||||
#ifndef NDEBUG
|
||||
debug_check();
|
||||
#endif
|
||||
}
|
||||
|
||||
void debug_check_contains(T* item)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
debug_check();
|
||||
T* curr = head;
|
||||
|
||||
while (curr != item)
|
||||
{
|
||||
assert(curr != (T*)terminator);
|
||||
curr = curr->next;
|
||||
}
|
||||
#else
|
||||
UNUSED(item);
|
||||
#endif
|
||||
}
|
||||
|
||||
void debug_check_not_contains(T* item)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
debug_check();
|
||||
T* curr = head;
|
||||
|
||||
while (curr != (T*)terminator)
|
||||
{
|
||||
assert(curr != item);
|
||||
curr = curr->next;
|
||||
}
|
||||
#else
|
||||
UNUSED(item);
|
||||
#endif
|
||||
}
|
||||
|
||||
void debug_check()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
T* item = head;
|
||||
T* prev = (T*)terminator;
|
||||
|
||||
while (item != (T*)terminator)
|
||||
{
|
||||
assert(item->prev == prev);
|
||||
prev = item;
|
||||
item = item->next;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
}
|
||||
24
src/ds/flaglock.h
Normal file
24
src/ds/flaglock.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "bits.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class FlagLock
|
||||
{
|
||||
private:
|
||||
std::atomic_flag& lock;
|
||||
|
||||
public:
|
||||
FlagLock(std::atomic_flag& lock) : lock(lock)
|
||||
{
|
||||
while (lock.test_and_set(std::memory_order_acquire))
|
||||
bits::pause();
|
||||
}
|
||||
|
||||
~FlagLock()
|
||||
{
|
||||
lock.clear(std::memory_order_release);
|
||||
}
|
||||
};
|
||||
}
|
||||
33
src/ds/helpers.h
Normal file
33
src/ds/helpers.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "flaglock.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
/*
|
||||
* In some use cases we need to run before any of the C++ runtime has been
|
||||
* initialised. This singleton class is design to not depend on the runtime.
|
||||
*/
|
||||
template<class Object, Object init() noexcept>
|
||||
class Singleton
|
||||
{
|
||||
public:
|
||||
inline static Object& get()
|
||||
{
|
||||
static std::atomic_flag flag;
|
||||
static std::atomic<bool> initialised;
|
||||
static Object obj;
|
||||
|
||||
if (!initialised.load(std::memory_order_acquire))
|
||||
{
|
||||
FlagLock lock(flag);
|
||||
if (!initialised)
|
||||
{
|
||||
obj = init();
|
||||
initialised.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
}
|
||||
75
src/ds/mpmcstack.h
Normal file
75
src/ds/mpmcstack.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "aba.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<class T, Construction c = RequiresInit>
|
||||
class MPMCStack
|
||||
{
|
||||
using ABA = ABA<T, c>;
|
||||
|
||||
private:
|
||||
static_assert(
|
||||
std::is_same<decltype(((T*)0)->next), std::atomic<T*>>::value,
|
||||
"T->next must be a std::atomic<T*>");
|
||||
|
||||
ABA stack;
|
||||
|
||||
public:
|
||||
void push(T* item)
|
||||
{
|
||||
return push(item, item);
|
||||
}
|
||||
|
||||
void push(T* first, T* last)
|
||||
{
|
||||
// Pushes an item on the stack.
|
||||
auto cmp = stack.read();
|
||||
|
||||
do
|
||||
{
|
||||
T* top = ABA::load(cmp);
|
||||
last->next.store(top, std::memory_order_release);
|
||||
} while (!stack.compare_exchange(cmp, first));
|
||||
}
|
||||
|
||||
T* pop()
|
||||
{
|
||||
// Returns the next item. If the returned value is decommitted, it is
|
||||
// possible for the read of top->next to segfault.
|
||||
auto cmp = stack.read();
|
||||
T* top;
|
||||
T* next;
|
||||
|
||||
do
|
||||
{
|
||||
top = ABA::load(cmp);
|
||||
|
||||
if (top == nullptr)
|
||||
break;
|
||||
|
||||
next = top->next.load(std::memory_order_acquire);
|
||||
} while (!stack.compare_exchange(cmp, next));
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
T* pop_all()
|
||||
{
|
||||
// Returns all items as a linked list, leaving an empty stack.
|
||||
auto cmp = stack.read();
|
||||
T* top;
|
||||
|
||||
do
|
||||
{
|
||||
top = ABA::load(cmp);
|
||||
|
||||
if (top == nullptr)
|
||||
break;
|
||||
} while (!stack.compare_exchange(cmp, nullptr));
|
||||
|
||||
return top;
|
||||
}
|
||||
};
|
||||
}
|
||||
99
src/ds/mpscq.h
Normal file
99
src/ds/mpscq.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include "bits.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <utility>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<class T>
|
||||
class MPSCQ
|
||||
{
|
||||
private:
|
||||
static_assert(
|
||||
std::is_same<decltype(((T*)0)->next), std::atomic<T*>>::value,
|
||||
"T->next must be a std::atomic<T*>");
|
||||
|
||||
std::atomic<T*> head;
|
||||
T* tail;
|
||||
|
||||
public:
|
||||
void invariant()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
assert(head != nullptr);
|
||||
assert(tail != nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void init(T* stub)
|
||||
{
|
||||
stub->next.store(nullptr, std::memory_order_relaxed);
|
||||
tail = stub;
|
||||
head.store(stub, std::memory_order_relaxed);
|
||||
invariant();
|
||||
}
|
||||
|
||||
T* destroy()
|
||||
{
|
||||
T* tl = tail;
|
||||
head.store(nullptr, std::memory_order_relaxed);
|
||||
tail = nullptr;
|
||||
return tl;
|
||||
}
|
||||
|
||||
T* get_head()
|
||||
{
|
||||
return head.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
inline void push(T* item)
|
||||
{
|
||||
push(item, item);
|
||||
}
|
||||
|
||||
inline bool is_empty()
|
||||
{
|
||||
T* hd = head.load(std::memory_order_relaxed);
|
||||
|
||||
return hd == tail;
|
||||
}
|
||||
|
||||
void push(T* first, T* last)
|
||||
{
|
||||
// Pushes a list of messages to the queue. Each message from first to
|
||||
// last should be linked together through their next pointers.
|
||||
invariant();
|
||||
last->next.store(nullptr, std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
T* prev = head.exchange(last, std::memory_order_relaxed);
|
||||
prev->next.store(first, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::pair<T*, T*> pop()
|
||||
{
|
||||
// Returns the next message and the tail message. If the next message
|
||||
// is not null, the tail message should be freed by the caller.
|
||||
invariant();
|
||||
T* tl = tail;
|
||||
T* next = tl->next.load(std::memory_order_relaxed);
|
||||
|
||||
if (next != nullptr)
|
||||
{
|
||||
tail = next;
|
||||
|
||||
assert(tail);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
invariant();
|
||||
return std::make_pair(next, tl);
|
||||
}
|
||||
|
||||
T* peek()
|
||||
{
|
||||
return tail->next.load(std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
}
|
||||
1091
src/mem/alloc.h
Normal file
1091
src/mem/alloc.h
Normal file
File diff suppressed because it is too large
Load Diff
121
src/mem/allocconfig.h
Normal file
121
src/mem/allocconfig.h
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/bits.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
enum ZeroMem
|
||||
{
|
||||
NoZero,
|
||||
YesZero
|
||||
};
|
||||
|
||||
// 0 intermediate bits results in power of 2 small allocs. 1 intermediate
|
||||
// bit gives additional sizeclasses at the midpoint between each power of 2.
|
||||
// 2 intermediate bits gives 3 intermediate sizeclasses, etc.
|
||||
static constexpr size_t INTERMEDIATE_BITS =
|
||||
#ifdef USE_INTERMEDIATE_BITS
|
||||
USE_INTERMEDIATE_BITS
|
||||
#else
|
||||
2
|
||||
#endif
|
||||
;
|
||||
|
||||
// Return remote small allocs when the local cache reaches this size.
|
||||
static constexpr size_t REMOTE_CACHE =
|
||||
#ifdef USE_REMOTE_CACHE
|
||||
USE_REMOTE_CACHE
|
||||
#else
|
||||
1 << 20
|
||||
#endif
|
||||
;
|
||||
|
||||
// Handle at most this many object from the remote dealloc queue at a time.
|
||||
static constexpr size_t REMOTE_BATCH =
|
||||
#ifdef USE_REMOTE_BATCH
|
||||
REMOTE_BATCH
|
||||
#else
|
||||
64
|
||||
#endif
|
||||
;
|
||||
|
||||
static constexpr size_t RESERVE_MULTIPLE =
|
||||
#ifdef USE_RESERVE_MULTIPLE
|
||||
USE_RESERVE_MULTIPLE
|
||||
#else
|
||||
bits::is64() ? 16 : 2
|
||||
#endif
|
||||
;
|
||||
|
||||
enum DecommitStrategy
|
||||
{
|
||||
DecommitNone,
|
||||
DecommitSuper,
|
||||
DecommitAll
|
||||
};
|
||||
|
||||
static constexpr DecommitStrategy decommit_strategy =
|
||||
#ifdef USE_DECOMMIT_STRATEGY
|
||||
USE_DECOMMIT_STRATEGY
|
||||
#else
|
||||
DecommitSuper
|
||||
#endif
|
||||
;
|
||||
|
||||
// The remaining values are derived, not configurable.
|
||||
|
||||
// Used to isolate values on cache lines to prevent false sharing.
|
||||
static constexpr size_t CACHELINE_SIZE = 64;
|
||||
|
||||
// Used to keep Superslab metadata committed.
|
||||
static constexpr size_t OS_PAGE_SIZE = 0x1000;
|
||||
static constexpr size_t PAGE_ALIGNED_SIZE = OS_PAGE_SIZE << INTERMEDIATE_BITS;
|
||||
// Some system headers (e.g. Linux' sys/user.h, FreeBSD's machine/param.h)
|
||||
// define `PAGE_SIZE` as a macro. We don't use `PAGE_SIZE` as our variable
|
||||
// name, to avoid conflicts, but if we do see a macro definition then check
|
||||
// that our value matches the platform's expected value.
|
||||
#ifdef PAGE_SIZE
|
||||
static_assert(
|
||||
PAGE_SIZE == OS_PAGE_SIZE,
|
||||
"Page size from system header does not match snmalloc config page size.");
|
||||
#endif
|
||||
|
||||
// Minimum allocation size is space for two pointers.
|
||||
static constexpr size_t MIN_ALLOC_BITS = bits::is64() ? 4 : 3;
|
||||
static constexpr size_t MIN_ALLOC_SIZE = 1 << MIN_ALLOC_BITS;
|
||||
|
||||
// Slabs are 64 kb.
|
||||
static constexpr size_t SLAB_BITS = 16;
|
||||
static constexpr size_t SLAB_SIZE = 1 << SLAB_BITS;
|
||||
static constexpr size_t SLAB_MASK = ~(SLAB_SIZE - 1);
|
||||
|
||||
// Superslabs are composed of this many slabs. Slab offsets are encoded as
|
||||
// a byte, so the maximum count is 256. This must be a power of two to
|
||||
// allow fast masking to find a superslab start address.
|
||||
static constexpr size_t SLAB_COUNT_BITS = 8;
|
||||
static constexpr size_t SLAB_COUNT = 1 << SLAB_COUNT_BITS;
|
||||
static constexpr size_t SUPERSLAB_SIZE = SLAB_SIZE * SLAB_COUNT;
|
||||
static constexpr size_t SUPERSLAB_MASK = ~(SUPERSLAB_SIZE - 1);
|
||||
static constexpr size_t SUPERSLAB_BITS = SLAB_BITS + SLAB_COUNT_BITS;
|
||||
static constexpr size_t RESERVE_SIZE = SUPERSLAB_SIZE * RESERVE_MULTIPLE;
|
||||
|
||||
// Number of slots for remote deallocation.
|
||||
static constexpr size_t REMOTE_SLOT_BITS = 6;
|
||||
static constexpr size_t REMOTE_SLOTS = 1 << REMOTE_SLOT_BITS;
|
||||
static constexpr size_t REMOTE_MASK = REMOTE_SLOTS - 1;
|
||||
|
||||
static_assert(
|
||||
INTERMEDIATE_BITS < MIN_ALLOC_BITS,
|
||||
"INTERMEDIATE_BITS must be less than MIN_ALLOC_BITS");
|
||||
static_assert(
|
||||
MIN_ALLOC_SIZE >= (sizeof(void*) * 2),
|
||||
"MIN_ALLOC_SIZE must be sufficient for two pointers");
|
||||
static_assert(
|
||||
SLAB_BITS == (sizeof(uint16_t) * 8),
|
||||
"SLAB_BITS must be the bits in a uint16_t");
|
||||
static_assert(
|
||||
SLAB_COUNT == bits::next_pow2_const(SLAB_COUNT),
|
||||
"SLAB_COUNT must be a power of 2");
|
||||
static_assert(
|
||||
SLAB_COUNT <= (UINT8_MAX + 1), "SLAB_COUNT must fit in a uint8_t");
|
||||
};
|
||||
19
src/mem/allocslab.h
Normal file
19
src/mem/allocslab.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "../mem/baseslab.h"
|
||||
#include "remoteallocator.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class Allocslab : public Baseslab
|
||||
{
|
||||
protected:
|
||||
RemoteAllocator* allocator;
|
||||
|
||||
public:
|
||||
RemoteAllocator* get_allocator()
|
||||
{
|
||||
return allocator;
|
||||
}
|
||||
};
|
||||
}
|
||||
391
src/mem/allocstats.h
Normal file
391
src/mem/allocstats.h
Normal file
@@ -0,0 +1,391 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/bits.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
# include "../ds/csv.h"
|
||||
# include "sizeclass.h"
|
||||
|
||||
# include <cstring>
|
||||
# include <iostream>
|
||||
#endif
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<size_t N, size_t LARGE_N>
|
||||
struct AllocStats
|
||||
{
|
||||
struct CurrentMaxPair
|
||||
{
|
||||
size_t current = 0;
|
||||
size_t max = 0;
|
||||
|
||||
void inc()
|
||||
{
|
||||
current++;
|
||||
if (current > max)
|
||||
max++;
|
||||
}
|
||||
|
||||
void dec()
|
||||
{
|
||||
current--;
|
||||
}
|
||||
|
||||
bool is_empty()
|
||||
{
|
||||
return current == 0;
|
||||
}
|
||||
|
||||
bool is_unused()
|
||||
{
|
||||
return max == 0;
|
||||
}
|
||||
|
||||
void add(CurrentMaxPair& that)
|
||||
{
|
||||
current += that.current;
|
||||
max += that.max;
|
||||
}
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
void print(CSVStream& csv, size_t multiplier = 1)
|
||||
{
|
||||
csv << current * multiplier << max * multiplier;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
struct Stats
|
||||
{
|
||||
CurrentMaxPair count;
|
||||
CurrentMaxPair slab_count;
|
||||
uint64_t time = bits::tick();
|
||||
uint64_t ticks = 0;
|
||||
double online_average = 0;
|
||||
|
||||
bool is_empty()
|
||||
{
|
||||
return count.is_empty();
|
||||
}
|
||||
|
||||
void add(Stats& that)
|
||||
{
|
||||
count.add(that.count);
|
||||
slab_count.add(that.slab_count);
|
||||
}
|
||||
|
||||
void addToRunningAverage()
|
||||
{
|
||||
uint64_t now = bits::tick();
|
||||
|
||||
if (slab_count.current != 0)
|
||||
{
|
||||
double occupancy = (double)count.current / (double)slab_count.current;
|
||||
uint64_t duration = now - time;
|
||||
|
||||
if (ticks == 0)
|
||||
online_average = occupancy;
|
||||
else
|
||||
online_average += ((occupancy - online_average) * duration) / ticks;
|
||||
|
||||
ticks += duration;
|
||||
}
|
||||
|
||||
time = now;
|
||||
}
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
void
|
||||
print(CSVStream& csv, size_t multiplier = 1, size_t slab_multiplier = 1)
|
||||
{
|
||||
// Keep in sync with header lower down
|
||||
count.print(csv, multiplier);
|
||||
slab_count.print(csv, slab_multiplier);
|
||||
size_t average = (size_t)(online_average * multiplier);
|
||||
|
||||
csv << average << (slab_multiplier - average) * slab_count.max
|
||||
<< csv.endl;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
static constexpr size_t BUCKETS_BITS = 4;
|
||||
static constexpr size_t BUCKETS = 1 << BUCKETS_BITS;
|
||||
static constexpr size_t TOTAL_BUCKETS =
|
||||
bits::to_exp_mant_const<BUCKETS_BITS>(
|
||||
((size_t)1 << (bits::ADDRESS_BITS - 1)));
|
||||
|
||||
Stats sizeclass[N];
|
||||
Stats large[LARGE_N];
|
||||
|
||||
size_t remote_freed = 0;
|
||||
size_t remote_posted = 0;
|
||||
size_t remote_received = 0;
|
||||
size_t superslab_push_count = 0;
|
||||
size_t superslab_pop_count = 0;
|
||||
size_t superslab_fresh_count = 0;
|
||||
size_t segment_count = 0;
|
||||
size_t bucketed_requests[TOTAL_BUCKETS] = {};
|
||||
#endif
|
||||
|
||||
void alloc_request(size_t size)
|
||||
{
|
||||
UNUSED(size);
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
bucketed_requests[bits::to_exp_mant<BUCKETS_BITS>(size)]++;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_empty()
|
||||
{
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
if (!sizeclass[i].is_empty())
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < LARGE_N; i++)
|
||||
{
|
||||
if (!large[i].is_empty())
|
||||
return false;
|
||||
}
|
||||
|
||||
return (remote_freed == remote_posted);
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void sizeclass_alloc(uint8_t sc)
|
||||
{
|
||||
UNUSED(sc);
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
sizeclass[sc].addToRunningAverage();
|
||||
sizeclass[sc].count.inc();
|
||||
#endif
|
||||
}
|
||||
|
||||
void sizeclass_dealloc(uint8_t sc)
|
||||
{
|
||||
UNUSED(sc);
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
sizeclass[sc].addToRunningAverage();
|
||||
sizeclass[sc].count.dec();
|
||||
#endif
|
||||
}
|
||||
|
||||
void large_alloc(size_t sc)
|
||||
{
|
||||
UNUSED(sc);
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
large[sc].count.inc();
|
||||
#endif
|
||||
}
|
||||
|
||||
void sizeclass_alloc_slab(uint8_t sc)
|
||||
{
|
||||
UNUSED(sc);
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
sizeclass[sc].addToRunningAverage();
|
||||
sizeclass[sc].slab_count.inc();
|
||||
#endif
|
||||
}
|
||||
|
||||
void sizeclass_dealloc_slab(uint8_t sc)
|
||||
{
|
||||
UNUSED(sc);
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
sizeclass[sc].addToRunningAverage();
|
||||
sizeclass[sc].slab_count.dec();
|
||||
#endif
|
||||
}
|
||||
|
||||
void large_dealloc(size_t sc)
|
||||
{
|
||||
UNUSED(sc);
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
large[sc].count.dec();
|
||||
#endif
|
||||
}
|
||||
|
||||
void segment_create()
|
||||
{
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
segment_count++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void superslab_pop()
|
||||
{
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
superslab_pop_count++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void superslab_push()
|
||||
{
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
superslab_push_count++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void superslab_fresh()
|
||||
{
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
superslab_fresh_count++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void remote_free(uint8_t sc)
|
||||
{
|
||||
UNUSED(sc);
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
remote_freed += sizeclass_to_size(sc);
|
||||
#endif
|
||||
}
|
||||
|
||||
void remote_post()
|
||||
{
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
remote_posted = remote_freed;
|
||||
#endif
|
||||
}
|
||||
|
||||
void remote_receive(uint8_t sc)
|
||||
{
|
||||
UNUSED(sc);
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
remote_received += sizeclass_to_size(sc);
|
||||
#endif
|
||||
}
|
||||
|
||||
void add(AllocStats<N, LARGE_N>& that)
|
||||
{
|
||||
UNUSED(that);
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
for (size_t i = 0; i < N; i++)
|
||||
sizeclass[i].add(that.sizeclass[i]);
|
||||
|
||||
for (size_t i = 0; i < LARGE_N; i++)
|
||||
large[i].add(that.large[i]);
|
||||
|
||||
for (size_t i = 0; i < TOTAL_BUCKETS; i++)
|
||||
bucketed_requests[i] += that.bucketed_requests[i];
|
||||
|
||||
remote_freed += that.remote_freed;
|
||||
remote_posted += that.remote_posted;
|
||||
remote_received += that.remote_received;
|
||||
superslab_pop_count += that.superslab_pop_count;
|
||||
superslab_push_count += that.superslab_push_count;
|
||||
superslab_fresh_count += that.superslab_fresh_count;
|
||||
segment_count += that.segment_count;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
template<class Alloc>
|
||||
void print(std::ostream& o, uint64_t dumpid = 0, uint64_t allocatorid = 0)
|
||||
{
|
||||
UNUSED(o);
|
||||
UNUSED(dumpid);
|
||||
UNUSED(allocatorid);
|
||||
|
||||
CSVStream csv(&o);
|
||||
|
||||
if (dumpid == 0)
|
||||
{
|
||||
// Output headers for initial dump
|
||||
// Keep in sync with data dump
|
||||
csv << "GlobalStats"
|
||||
<< "DumpID"
|
||||
<< "AllocatorID"
|
||||
<< "Remote freed"
|
||||
<< "Remote posted"
|
||||
<< "Remote received"
|
||||
<< "Superslab pop"
|
||||
<< "Superslab push"
|
||||
<< "Superslab fresh"
|
||||
<< "Segments" << csv.endl;
|
||||
|
||||
csv << "BucketedStats"
|
||||
<< "DumpID"
|
||||
<< "AllocatorID"
|
||||
<< "Size group"
|
||||
<< "Size"
|
||||
<< "Current bytes"
|
||||
<< "Max bytes"
|
||||
<< "Current Slab bytes"
|
||||
<< "Max Slab bytes"
|
||||
<< "Average Slab Usage"
|
||||
<< "Average wasted space" << csv.endl;
|
||||
|
||||
csv << "AllocSizes"
|
||||
<< "DumpID"
|
||||
<< "AllocatorID"
|
||||
<< "ClassID"
|
||||
<< "Low size"
|
||||
<< "High size"
|
||||
<< "Count" << csv.endl;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < N; i++)
|
||||
{
|
||||
if (sizeclass[i].count.is_unused())
|
||||
continue;
|
||||
|
||||
sizeclass[i].addToRunningAverage();
|
||||
|
||||
csv << "BucketedStats" << dumpid << allocatorid << i
|
||||
<< sizeclass_to_size(i);
|
||||
|
||||
sizeclass[i].print(csv, sizeclass_to_size(i), SLAB_SIZE);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < LARGE_N; i++)
|
||||
{
|
||||
if (large[i].count.is_unused())
|
||||
continue;
|
||||
|
||||
csv << "BucketedStats" << dumpid << allocatorid << (i + N)
|
||||
<< large_sizeclass_to_size(i);
|
||||
|
||||
large[i].print(csv, large_sizeclass_to_size(i));
|
||||
}
|
||||
|
||||
size_t low = 0;
|
||||
size_t high = 0;
|
||||
|
||||
for (size_t i = 0; i < TOTAL_BUCKETS; i++)
|
||||
{
|
||||
low = high + 1;
|
||||
high = bits::from_exp_mant<BUCKETS_BITS>(i);
|
||||
|
||||
if (bucketed_requests[i] == 0)
|
||||
continue;
|
||||
|
||||
csv << "AllocSizes" << dumpid << allocatorid << i << low << high
|
||||
<< bucketed_requests[i] << csv.endl;
|
||||
}
|
||||
|
||||
csv << "GlobalStats" << dumpid << allocatorid << remote_freed
|
||||
<< remote_posted << remote_received << superslab_pop_count
|
||||
<< superslab_push_count << superslab_fresh_count << segment_count
|
||||
<< csv.endl;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
}
|
||||
27
src/mem/baseslab.h
Normal file
27
src/mem/baseslab.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/mpmcstack.h"
|
||||
#include "allocconfig.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
enum SlabKind
|
||||
{
|
||||
Fresh = 0,
|
||||
Large,
|
||||
Medium,
|
||||
Super
|
||||
};
|
||||
|
||||
class Baseslab
|
||||
{
|
||||
protected:
|
||||
SlabKind kind;
|
||||
|
||||
public:
|
||||
SlabKind get_kind()
|
||||
{
|
||||
return kind;
|
||||
}
|
||||
};
|
||||
}
|
||||
171
src/mem/globalalloc.h
Normal file
171
src/mem/globalalloc.h
Normal file
@@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/helpers.h"
|
||||
#include "alloc.h"
|
||||
#include "typealloc.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<class MemoryProvider>
|
||||
class AllocPool : TypeAlloc<Allocator<MemoryProvider>, MemoryProvider>
|
||||
{
|
||||
using Alloc = Allocator<MemoryProvider>;
|
||||
using Parent = TypeAlloc<Allocator<MemoryProvider>, MemoryProvider>;
|
||||
|
||||
public:
|
||||
static AllocPool* make(MemoryProvider& mp)
|
||||
{
|
||||
static_assert(
|
||||
sizeof(AllocPool) == sizeof(Parent),
|
||||
"You cannot add fields to this class.");
|
||||
// This cast is safe due to the static assert.
|
||||
return (AllocPool*)Parent::make(mp);
|
||||
}
|
||||
|
||||
static AllocPool* make() noexcept
|
||||
{
|
||||
return make(default_memory_provider);
|
||||
}
|
||||
|
||||
Alloc* acquire()
|
||||
{
|
||||
return Parent::alloc(Parent::memory_provider);
|
||||
}
|
||||
|
||||
void release(Alloc* a)
|
||||
{
|
||||
Parent::dealloc(a);
|
||||
}
|
||||
|
||||
public:
|
||||
void aggregate_stats(Stats& stats)
|
||||
{
|
||||
auto* alloc = Parent::iterate();
|
||||
|
||||
while (alloc != nullptr)
|
||||
{
|
||||
stats.add(alloc->stats());
|
||||
alloc = Parent::iterate(alloc);
|
||||
}
|
||||
}
|
||||
|
||||
void print_all_stats(std::ostream& o, uint64_t dumpid = 0)
|
||||
{
|
||||
auto alloc = Parent::iterate();
|
||||
|
||||
while (alloc != nullptr)
|
||||
{
|
||||
alloc->stats().template print<Alloc>(o, dumpid, alloc->id());
|
||||
alloc = Parent::iterate(alloc);
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup_unused()
|
||||
{
|
||||
#ifndef USE_MALLOC
|
||||
// Call this periodically to free and coalesce memory allocated by
|
||||
// allocators that are not currently in use by any thread.
|
||||
// One atomic operation to extract the stack, another to restore it.
|
||||
// Handling the message queue for each stack is non-atomic.
|
||||
auto* first = Parent::extract();
|
||||
auto* alloc = first;
|
||||
decltype(alloc) last;
|
||||
|
||||
if (alloc != nullptr)
|
||||
{
|
||||
while (alloc != nullptr)
|
||||
{
|
||||
alloc->handle_message_queue();
|
||||
last = alloc;
|
||||
alloc = Parent::extract(alloc);
|
||||
}
|
||||
|
||||
restore(first, last);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void debug_check_empty()
|
||||
{
|
||||
#ifndef USE_MALLOC
|
||||
// This is a debugging function. It checks that all memory from all
|
||||
// allocators has been freed.
|
||||
size_t alloc_count = 0;
|
||||
|
||||
auto* alloc = Parent::iterate();
|
||||
|
||||
// Count the linked allocators.
|
||||
while (alloc != nullptr)
|
||||
{
|
||||
alloc = Parent::iterate(alloc);
|
||||
alloc_count++;
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
|
||||
while (!done)
|
||||
{
|
||||
done = true;
|
||||
alloc = Parent::iterate();
|
||||
|
||||
while (alloc != nullptr)
|
||||
{
|
||||
// Destroy the message queue so that it has no stub message.
|
||||
Remote* p = alloc->message_queue().destroy();
|
||||
|
||||
while (p != nullptr)
|
||||
{
|
||||
Remote* next = p->non_atomic_next;
|
||||
alloc->handle_dealloc_remote(p);
|
||||
p = next;
|
||||
}
|
||||
|
||||
// Place the static stub message on the queue.
|
||||
alloc->init_message_queue();
|
||||
|
||||
// Post all remotes, including forwarded ones. If any allocator posts,
|
||||
// repeat the loop.
|
||||
if (alloc->remote.size > 0)
|
||||
{
|
||||
alloc->stats().remote_post();
|
||||
alloc->remote.post(alloc->id());
|
||||
done = false;
|
||||
}
|
||||
|
||||
alloc = Parent::iterate(alloc);
|
||||
}
|
||||
}
|
||||
|
||||
alloc = Parent::iterate();
|
||||
size_t empty_count = 0;
|
||||
|
||||
while (alloc != nullptr)
|
||||
{
|
||||
// Check that the allocator has freed all memory.
|
||||
if (alloc->stats().is_empty())
|
||||
empty_count++;
|
||||
|
||||
alloc = Parent::iterate(alloc);
|
||||
}
|
||||
|
||||
if (alloc_count != empty_count)
|
||||
error("Incorrect number of allocators");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
inline AllocPool<GlobalVirtual>*& current_alloc_pool()
|
||||
{
|
||||
return Singleton<
|
||||
AllocPool<GlobalVirtual>*,
|
||||
AllocPool<GlobalVirtual>::make>::get();
|
||||
}
|
||||
|
||||
template<class MemoryProvider>
|
||||
inline AllocPool<MemoryProvider>* make_alloc_pool(MemoryProvider& mp)
|
||||
{
|
||||
return AllocPool<MemoryProvider>::make(mp);
|
||||
}
|
||||
|
||||
using Alloc = Allocator<GlobalVirtual>;
|
||||
}
|
||||
207
src/mem/largealloc.h
Normal file
207
src/mem/largealloc.h
Normal file
@@ -0,0 +1,207 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/flaglock.h"
|
||||
#include "../ds/mpmcstack.h"
|
||||
#include "../pal/pal.h"
|
||||
#include "allocstats.h"
|
||||
#include "baseslab.h"
|
||||
#include "sizeclass.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class Largeslab : public Baseslab
|
||||
{
|
||||
// This is the view of a contiguous memory area when it is being kept
|
||||
// in the global size-classed caches of available contiguous memory areas.
|
||||
private:
|
||||
template<class a, Construction c>
|
||||
friend class MPMCStack;
|
||||
std::atomic<Largeslab*> next;
|
||||
|
||||
public:
|
||||
void init()
|
||||
{
|
||||
kind = Large;
|
||||
}
|
||||
};
|
||||
|
||||
// This represents the state that the large allcoator needs to add to the
|
||||
// global state of the allocator. This is currently stored in the memory
|
||||
// provider, so we add this in.
|
||||
template<class MemoryProviderState>
|
||||
class MemoryProviderStateMixin : public MemoryProviderState
|
||||
{
|
||||
std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||
size_t bump;
|
||||
size_t remaining;
|
||||
|
||||
std::pair<void*, size_t> reserve_block() noexcept
|
||||
{
|
||||
size_t size = SUPERSLAB_SIZE;
|
||||
void* r = ((MemoryProviderState*)this)
|
||||
->template reserve<false>(&size, SUPERSLAB_SIZE);
|
||||
|
||||
if (size < SUPERSLAB_SIZE)
|
||||
error("out of memory");
|
||||
|
||||
((MemoryProviderState*)this)
|
||||
->template notify_using<NoZero>(r, OS_PAGE_SIZE);
|
||||
return std::make_pair(r, size);
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Stack of large allocations that have been returned for reuse.
|
||||
*/
|
||||
MPMCStack<Largeslab, PreZeroed> large_stack[NUM_LARGE_CLASSES];
|
||||
|
||||
/**
|
||||
* Primitive allocator for structure that are required before
|
||||
* the allocator can be running.
|
||||
***/
|
||||
void* alloc_chunk(size_t size)
|
||||
{
|
||||
// Cache line align
|
||||
size = bits::align_up(size, 64);
|
||||
|
||||
void* p;
|
||||
{
|
||||
FlagLock f(lock);
|
||||
|
||||
if (remaining < size)
|
||||
{
|
||||
auto r_size = reserve_block();
|
||||
|
||||
bump = (size_t)r_size.first;
|
||||
remaining = r_size.second;
|
||||
}
|
||||
|
||||
p = (void*)bump;
|
||||
bump += size;
|
||||
remaining -= size;
|
||||
}
|
||||
|
||||
auto page_start = bits::align_down((size_t)p, OS_PAGE_SIZE);
|
||||
auto page_end = bits::align_up((size_t)p + size, OS_PAGE_SIZE);
|
||||
|
||||
((MemoryProviderState*)this)
|
||||
->template notify_using<NoZero>(
|
||||
(void*)page_start, page_end - page_start);
|
||||
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
using Stats = AllocStats<NUM_SIZECLASSES, NUM_LARGE_CLASSES>;
|
||||
|
||||
enum AllowReserve
|
||||
{
|
||||
NoReserve,
|
||||
YesReserve
|
||||
};
|
||||
|
||||
template<class MemoryProvider>
|
||||
class LargeAlloc
|
||||
{
|
||||
void* reserved_start = nullptr;
|
||||
void* reserved_end = nullptr;
|
||||
|
||||
public:
|
||||
// This will be a zero-size structure if stats are not enabled.
|
||||
Stats stats;
|
||||
|
||||
MemoryProvider& memory_provider;
|
||||
|
||||
LargeAlloc(MemoryProvider& mp) : memory_provider(mp) {}
|
||||
|
||||
template<AllowReserve allow_reserve>
|
||||
bool reserve_memory(size_t need, size_t add)
|
||||
{
|
||||
if (((size_t)reserved_start + need) > (size_t)reserved_end)
|
||||
{
|
||||
if (allow_reserve == YesReserve)
|
||||
{
|
||||
stats.segment_create();
|
||||
reserved_start =
|
||||
memory_provider.template reserve<false>(&add, SUPERSLAB_SIZE);
|
||||
reserved_end = (void*)((size_t)reserved_start + add);
|
||||
reserved_start =
|
||||
(void*)bits::align_up((size_t)reserved_start, SUPERSLAB_SIZE);
|
||||
|
||||
if (add < need)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<ZeroMem zero_mem = NoZero, AllowReserve allow_reserve = YesReserve>
|
||||
void* alloc(size_t large_class, size_t size)
|
||||
{
|
||||
size_t rsize = ((size_t)1 << SUPERSLAB_BITS) << large_class;
|
||||
if (size == 0)
|
||||
size = rsize;
|
||||
|
||||
void* p = memory_provider.large_stack[large_class].pop();
|
||||
|
||||
if (p == nullptr)
|
||||
{
|
||||
assert(reserved_start <= reserved_end);
|
||||
size_t add;
|
||||
|
||||
if ((rsize + SUPERSLAB_SIZE) < RESERVE_SIZE)
|
||||
add = RESERVE_SIZE;
|
||||
else
|
||||
add = rsize + SUPERSLAB_SIZE;
|
||||
|
||||
if (!reserve_memory<allow_reserve>(rsize, add))
|
||||
return nullptr;
|
||||
|
||||
p = (void*)reserved_start;
|
||||
reserved_start = (void*)((size_t)p + rsize);
|
||||
|
||||
// All memory is zeroed since it comes from reserved space.
|
||||
memory_provider.template notify_using<NoZero>(p, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((decommit_strategy != DecommitNone) || (large_class > 0))
|
||||
{
|
||||
// Only the first page needs to be zeroed, as this was decommitted.
|
||||
if (zero_mem == YesZero)
|
||||
memory_provider.template zero<true>(p, OS_PAGE_SIZE);
|
||||
|
||||
memory_provider.template notify_using<zero_mem>(
|
||||
(void*)((size_t)p + OS_PAGE_SIZE), size - OS_PAGE_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a superslab that has not been decommitted.
|
||||
if (zero_mem == YesZero)
|
||||
memory_provider.template zero<true>(p, size);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void dealloc(void* p, size_t large_class)
|
||||
{
|
||||
memory_provider.large_stack[large_class].push((Largeslab*)p);
|
||||
}
|
||||
};
|
||||
|
||||
using GlobalVirtual = MemoryProviderStateMixin<Pal>;
|
||||
/**
|
||||
* The memory provider that will be used if no other provider is explicitly
|
||||
* passed as an argument.
|
||||
*/
|
||||
HEADER_GLOBAL GlobalVirtual default_memory_provider;
|
||||
}
|
||||
130
src/mem/mediumslab.h
Normal file
130
src/mem/mediumslab.h
Normal file
@@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/dllist.h"
|
||||
#include "allocconfig.h"
|
||||
#include "allocslab.h"
|
||||
#include "sizeclass.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class Mediumslab : public Allocslab
|
||||
{
|
||||
// This is the view of a 16 mb area when it is being used to allocate
|
||||
// medium sized classes: 64 kb to 16 mb, non-inclusive.
|
||||
private:
|
||||
friend DLList<Mediumslab>;
|
||||
|
||||
// Keep the allocator pointer on a separate cache line. It is read by
|
||||
// other threads, and does not change, so we avoid false sharing.
|
||||
alignas(CACHELINE_SIZE) Mediumslab* next;
|
||||
Mediumslab* prev;
|
||||
|
||||
uint16_t free;
|
||||
uint8_t head;
|
||||
uint8_t sizeclass;
|
||||
uint16_t stack[SLAB_COUNT - 1];
|
||||
|
||||
public:
|
||||
static constexpr uint32_t header_size()
|
||||
{
|
||||
static_assert(
|
||||
sizeof(Mediumslab) < OS_PAGE_SIZE,
|
||||
"Mediumslab header size must be less than the page size");
|
||||
static_assert(
|
||||
sizeof(Mediumslab) < SLAB_SIZE,
|
||||
"Mediumslab header size must be less than the slab size");
|
||||
|
||||
// Always use a full page as the header, in order to get page sized
|
||||
// alignment of individual allocations.
|
||||
return OS_PAGE_SIZE;
|
||||
}
|
||||
|
||||
static Mediumslab* get(void* p)
|
||||
{
|
||||
return (Mediumslab*)((size_t)p & SUPERSLAB_MASK);
|
||||
}
|
||||
|
||||
void init(RemoteAllocator* alloc, uint8_t sc, size_t rsize)
|
||||
{
|
||||
assert(sc >= NUM_SMALL_CLASSES);
|
||||
assert((sc - NUM_SMALL_CLASSES) < NUM_MEDIUM_CLASSES);
|
||||
|
||||
allocator = alloc;
|
||||
head = 0;
|
||||
|
||||
// If this was previously a Mediumslab of the same sizeclass, don't
|
||||
// initialise the allocation stack.
|
||||
if ((kind != Medium) || (sizeclass != sc))
|
||||
{
|
||||
sizeclass = sc;
|
||||
uint16_t ssize = (uint16_t)(rsize >> 8);
|
||||
kind = Medium;
|
||||
free = medium_slab_free(sc);
|
||||
for (uint16_t i = free; i > 0; i--)
|
||||
stack[free - i] = (uint16_t)((SUPERSLAB_SIZE >> 8) - (i * ssize));
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(free == medium_slab_free(sc));
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_sizeclass()
|
||||
{
|
||||
return sizeclass;
|
||||
}
|
||||
|
||||
template<ZeroMem zero_mem, typename MemoryProvider>
|
||||
void* alloc(size_t size, MemoryProvider& memory_provider)
|
||||
{
|
||||
assert(!full());
|
||||
|
||||
uint16_t index = stack[head++];
|
||||
void* p = (void*)((size_t)this + ((size_t)index << 8));
|
||||
free--;
|
||||
|
||||
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, OS_PAGE_SIZE));
|
||||
size = bits::align_up(size, OS_PAGE_SIZE);
|
||||
|
||||
if (decommit_strategy == DecommitAll)
|
||||
memory_provider.template notify_using<zero_mem>(p, size);
|
||||
else if (zero_mem == YesZero)
|
||||
memory_provider.template zero<true>(p, size);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
template<typename MemoryProvider>
|
||||
bool dealloc(void* p, MemoryProvider& memory_provider)
|
||||
{
|
||||
assert(head > 0);
|
||||
|
||||
// Returns true if the Mediumslab was full before this deallocation.
|
||||
bool was_full = full();
|
||||
free++;
|
||||
stack[--head] = pointer_to_index(p);
|
||||
|
||||
if (decommit_strategy == DecommitAll)
|
||||
memory_provider.notify_not_using(p, sizeclass_to_size(sizeclass));
|
||||
|
||||
return was_full;
|
||||
}
|
||||
|
||||
bool full()
|
||||
{
|
||||
return free == 0;
|
||||
}
|
||||
|
||||
bool empty()
|
||||
{
|
||||
return head == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t pointer_to_index(void* p)
|
||||
{
|
||||
// Get the offset from the slab for a memory location.
|
||||
return (uint16_t)(((size_t)p - (size_t)this) >> 8);
|
||||
}
|
||||
};
|
||||
}
|
||||
166
src/mem/metaslab.h
Normal file
166
src/mem/metaslab.h
Normal file
@@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/dllist.h"
|
||||
#include "sizeclass.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class Slab;
|
||||
|
||||
struct SlabLink
|
||||
{
|
||||
SlabLink* prev;
|
||||
SlabLink* next;
|
||||
|
||||
Slab* get_slab()
|
||||
{
|
||||
return (Slab*)((size_t)this & SLAB_MASK);
|
||||
}
|
||||
};
|
||||
|
||||
using SlabList = DLList<SlabLink, ~(uintptr_t)0>;
|
||||
|
||||
static_assert(
|
||||
sizeof(SlabLink) <= MIN_ALLOC_SIZE,
|
||||
"Need to be able to pack a SlabLink into any free small alloc");
|
||||
|
||||
static constexpr uint16_t SLABLINK_INDEX =
|
||||
(uint16_t)(SLAB_SIZE - sizeof(SlabLink));
|
||||
|
||||
// The Metaslab represent the status of a single slab.
|
||||
// This can be either a short or a standard slab.
|
||||
class Metaslab
|
||||
{
|
||||
private:
|
||||
// How many entries are used in this slab.
|
||||
uint16_t used;
|
||||
|
||||
public:
|
||||
// Bump free list of unused entries in this sizeclass.
|
||||
// If the bottom bit is 1, then this represents a bump_ptr
|
||||
// of where we have allocated up to in this slab. Otherwise,
|
||||
// it represents the location of the first block in the free
|
||||
// list. The free list is chained through deallocated blocks.
|
||||
// It either terminates with a bump ptr, or if all the space is in
|
||||
// the free list, then the last block will be also referenced by
|
||||
// link.
|
||||
// Note that, in the case that this is the first block in the size
|
||||
// class list, where all the unused memory is in the free list,
|
||||
// then the last block can both be interpreted as a final bump
|
||||
// pointer entry, and the first entry in the doubly linked list.
|
||||
// The terminal value in the free list, and the terminal value in
|
||||
// the SlabLink previous field will alias. The SlabLink uses ~0 for
|
||||
// its terminal value to be a valid terminal bump ptr.
|
||||
uint16_t head;
|
||||
// When a slab has free space it will be on the has space list for
|
||||
// that size class. We use an empty block in this slab to be the
|
||||
// doubly linked node into that size class's free list.
|
||||
uint16_t link;
|
||||
|
||||
union
|
||||
{
|
||||
uint8_t sizeclass;
|
||||
uint8_t next;
|
||||
};
|
||||
|
||||
void add_use()
|
||||
{
|
||||
used++;
|
||||
}
|
||||
|
||||
void sub_use()
|
||||
{
|
||||
used--;
|
||||
}
|
||||
|
||||
void set_unused()
|
||||
{
|
||||
used = 0;
|
||||
}
|
||||
|
||||
bool is_unused()
|
||||
{
|
||||
return used == 0;
|
||||
}
|
||||
|
||||
bool is_full()
|
||||
{
|
||||
return (head & 2) != 0;
|
||||
}
|
||||
|
||||
void set_full()
|
||||
{
|
||||
assert(head == 1);
|
||||
head = (uint16_t)~0;
|
||||
}
|
||||
|
||||
SlabLink* get_link(Slab* slab)
|
||||
{
|
||||
return (SlabLink*)((size_t)slab + link);
|
||||
}
|
||||
|
||||
bool valid_head(bool is_short)
|
||||
{
|
||||
size_t size = sizeclass_to_size(sizeclass);
|
||||
size_t offset = get_slab_offset(sizeclass, is_short);
|
||||
return ((((head & ~(size_t)1) - (offset & ~(size_t)1)) % size) == 0);
|
||||
}
|
||||
|
||||
void debug_slab_invariant(bool is_short, Slab* slab)
|
||||
{
|
||||
#if !defined(NDEBUG) && !defined(SNMALLOC_CHEAP_CHECKS)
|
||||
size_t size = sizeclass_to_size(sizeclass);
|
||||
size_t offset = get_slab_offset(sizeclass, is_short) - 1;
|
||||
|
||||
size_t accounted_for = used * size + offset;
|
||||
|
||||
if (is_full())
|
||||
{
|
||||
// All the blocks must be used.
|
||||
assert(SLAB_SIZE == accounted_for);
|
||||
// There is no free list to validate
|
||||
// 'link' value is not important if full.
|
||||
return;
|
||||
}
|
||||
// Block is not full
|
||||
assert(SLAB_SIZE > accounted_for);
|
||||
|
||||
// Walk bump-free-list-segment accounting for unused space
|
||||
uint16_t curr = head;
|
||||
while ((curr & 1) != 1)
|
||||
{
|
||||
// Check we are looking at a correctly aligned block
|
||||
assert((curr - offset) % size == 0);
|
||||
// Account for free elements in free list
|
||||
accounted_for += size;
|
||||
assert(SLAB_SIZE >= accounted_for);
|
||||
// We are not guaranteed to hit a bump ptr unless
|
||||
// we are the top element on the size class, so treat as
|
||||
// a list segment.
|
||||
if (curr == link)
|
||||
break;
|
||||
// Iterate bump/free list segment
|
||||
curr = *(uint16_t*)((uintptr_t)slab + curr);
|
||||
}
|
||||
|
||||
// Check we terminated traversal on a correctly aligned block
|
||||
assert(((curr & ~1) - offset) % size == 0);
|
||||
|
||||
if (curr != link)
|
||||
{
|
||||
// The link should be at the special end location as we
|
||||
// haven't completely filled this block at any point.
|
||||
assert(link == SLABLINK_INDEX);
|
||||
// Account for to be bump allocated space
|
||||
accounted_for += SLAB_SIZE - (curr - 1);
|
||||
}
|
||||
|
||||
// All space accounted for
|
||||
assert(SLAB_SIZE == accounted_for);
|
||||
#else
|
||||
UNUSED(slab);
|
||||
UNUSED(is_short);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
}
|
||||
227
src/mem/pagemap.h
Normal file
227
src/mem/pagemap.h
Normal file
@@ -0,0 +1,227 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/bits.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<size_t GRANULARITY_BITS, typename T, T default_content>
|
||||
class Pagemap
|
||||
{
|
||||
private:
|
||||
static constexpr size_t PAGEMAP_BITS = 16;
|
||||
static constexpr size_t PAGEMAP_SIZE = 1 << PAGEMAP_BITS;
|
||||
|
||||
static constexpr size_t COVERED_BITS =
|
||||
bits::ADDRESS_BITS - GRANULARITY_BITS;
|
||||
static constexpr size_t POINTER_BITS =
|
||||
bits::next_pow2_bits_const(sizeof(void*));
|
||||
static constexpr size_t CONTENT_BITS =
|
||||
bits::next_pow2_bits_const(sizeof(T));
|
||||
|
||||
static constexpr size_t BITS_FOR_LEAF = PAGEMAP_BITS - CONTENT_BITS;
|
||||
static constexpr size_t ENTRIES_PER_LEAF = 1 << BITS_FOR_LEAF;
|
||||
static constexpr size_t LEAF_MASK = ENTRIES_PER_LEAF - 1;
|
||||
|
||||
static constexpr size_t BITS_PER_INDEX_LEVEL = PAGEMAP_BITS - POINTER_BITS;
|
||||
static constexpr size_t ENTRIES_PER_INDEX_LEVEL = 1 << BITS_PER_INDEX_LEVEL;
|
||||
static constexpr size_t ENTRIES_MASK = ENTRIES_PER_INDEX_LEVEL - 1;
|
||||
|
||||
static constexpr size_t INDEX_BITS =
|
||||
BITS_FOR_LEAF > COVERED_BITS ? 0 : COVERED_BITS - BITS_FOR_LEAF;
|
||||
|
||||
static constexpr size_t INDEX_LEVELS = INDEX_BITS / BITS_PER_INDEX_LEVEL;
|
||||
static constexpr size_t TOPLEVEL_BITS =
|
||||
INDEX_BITS - (INDEX_LEVELS * BITS_PER_INDEX_LEVEL);
|
||||
static constexpr size_t TOPLEVEL_ENTRIES = 1 << TOPLEVEL_BITS;
|
||||
static constexpr size_t TOPLEVEL_SHIFT =
|
||||
(INDEX_LEVELS * BITS_PER_INDEX_LEVEL) + BITS_FOR_LEAF + GRANULARITY_BITS;
|
||||
|
||||
// Value used to represent when a node is being added too
|
||||
static constexpr uintptr_t LOCKED_ENTRY = 1;
|
||||
|
||||
struct Leaf
|
||||
{
|
||||
std::atomic<T> values[ENTRIES_PER_LEAF];
|
||||
};
|
||||
|
||||
struct PagemapEntry
|
||||
{
|
||||
std::atomic<PagemapEntry*> entries[ENTRIES_PER_INDEX_LEVEL];
|
||||
};
|
||||
|
||||
static_assert(
|
||||
sizeof(PagemapEntry) == sizeof(Leaf), "Should be the same size");
|
||||
|
||||
// Init removed as not required as this is only ever a global
|
||||
// cl is generating a memset of zero, which will be a problem
|
||||
// in libc/ucrt bring up. On ucrt this will run after the first
|
||||
// allocation.
|
||||
// TODO: This is fragile that it is not being memset, and we should review
|
||||
// to ensure we don't get bitten by this in the future.
|
||||
std::atomic<PagemapEntry*> top[TOPLEVEL_ENTRIES]; // = {nullptr};
|
||||
|
||||
template<bool create_addr>
|
||||
inline PagemapEntry* get_node(std::atomic<PagemapEntry*>* e, bool& result)
|
||||
{
|
||||
// The page map nodes are all allocated directly from the OS zero
|
||||
// initialised with a system call. We don't need any ordered to guarantee
|
||||
// to see that correctly.
|
||||
PagemapEntry* value = e->load(std::memory_order_relaxed);
|
||||
|
||||
if ((uintptr_t)value <= LOCKED_ENTRY)
|
||||
{
|
||||
if constexpr (create_addr)
|
||||
{
|
||||
value = nullptr;
|
||||
|
||||
if (e->compare_exchange_strong(
|
||||
value, (PagemapEntry*)LOCKED_ENTRY, std::memory_order_relaxed))
|
||||
{
|
||||
auto& v = default_memory_provider;
|
||||
value = (PagemapEntry*)v.alloc_chunk(PAGEMAP_SIZE);
|
||||
e->store(value, std::memory_order_release);
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((uintptr_t)e->load(std::memory_order_relaxed) ==
|
||||
LOCKED_ENTRY)
|
||||
{
|
||||
bits::pause();
|
||||
}
|
||||
value = e->load(std::memory_order_acquire);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = false;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
return value;
|
||||
}
|
||||
|
||||
template<bool create_addr>
|
||||
inline std::pair<Leaf*, size_t> get_leaf_index(void* p, bool& result)
|
||||
{
|
||||
size_t addr = (size_t)p;
|
||||
#ifdef FreeBSD_KERNEL
|
||||
// Zero the top 16 bits - kernel addresses all have them set, but the
|
||||
// data structure assumes that they're zero.
|
||||
addr &= 0xffffffffffffULL;
|
||||
#endif
|
||||
size_t ix = addr >> TOPLEVEL_SHIFT;
|
||||
size_t shift = TOPLEVEL_SHIFT;
|
||||
std::atomic<PagemapEntry*>* e = &top[ix];
|
||||
|
||||
for (size_t i = 0; i < INDEX_LEVELS; i++)
|
||||
{
|
||||
PagemapEntry* value = get_node<create_addr>(e, result);
|
||||
if (!result)
|
||||
return std::pair(nullptr, 0);
|
||||
|
||||
shift -= BITS_PER_INDEX_LEVEL;
|
||||
ix = (addr >> shift) & ENTRIES_MASK;
|
||||
e = &value->entries[ix];
|
||||
|
||||
if constexpr (INDEX_LEVELS == 1)
|
||||
{
|
||||
UNUSED(i);
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
|
||||
if (i == INDEX_LEVELS)
|
||||
break;
|
||||
}
|
||||
|
||||
Leaf* leaf = (Leaf*)get_node<create_addr>(e, result);
|
||||
|
||||
if (!result)
|
||||
return std::pair(nullptr, 0);
|
||||
|
||||
shift -= BITS_FOR_LEAF;
|
||||
ix = (addr >> shift) & LEAF_MASK;
|
||||
return std::pair(leaf, ix);
|
||||
}
|
||||
|
||||
template<bool create_addr>
|
||||
inline std::atomic<T>* get_addr(void* p, bool& success)
|
||||
{
|
||||
auto leaf_ix = get_leaf_index<create_addr>(p, success);
|
||||
return &(leaf_ix.first->values[leaf_ix.second]);
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr size_t GRANULARITY = 1 << GRANULARITY_BITS;
|
||||
|
||||
/**
|
||||
* Returns the index of a pagemap entry within a given page. This is used
|
||||
* in code that propagates changes to the pagemap elsewhere.
|
||||
*/
|
||||
size_t index_for_address(void* p)
|
||||
{
|
||||
bool success;
|
||||
return (OS_PAGE_SIZE - 1) &
|
||||
reinterpret_cast<size_t>(get_addr<true>(p, success));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address of the page containing
|
||||
*/
|
||||
void* page_for_address(void* p)
|
||||
{
|
||||
bool success;
|
||||
return reinterpret_cast<void*>(
|
||||
~(OS_PAGE_SIZE - 1) &
|
||||
reinterpret_cast<uintptr_t>(get_addr<true>(p, success)));
|
||||
}
|
||||
|
||||
std::atomic<T>* get_ptr(void* p)
|
||||
{
|
||||
bool success;
|
||||
return get_addr<true>(p, success);
|
||||
}
|
||||
|
||||
T get(void* p)
|
||||
{
|
||||
bool success;
|
||||
auto addr = get_addr<false>(p, success);
|
||||
if (!success)
|
||||
return default_content;
|
||||
return addr->load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void set(void* p, T x)
|
||||
{
|
||||
bool success;
|
||||
auto addr = get_addr<true>(p, success);
|
||||
addr->store(x, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void set_range(void* p, T x, size_t length)
|
||||
{
|
||||
bool success;
|
||||
do
|
||||
{
|
||||
auto leaf_ix = get_leaf_index<true>(p, success);
|
||||
size_t ix = leaf_ix.second;
|
||||
|
||||
auto last = std::min(LEAF_MASK + 1, ix + length);
|
||||
|
||||
auto diff = last - ix;
|
||||
|
||||
for (; ix < last; ix++)
|
||||
{
|
||||
leaf_ix.first->values[ix] = x;
|
||||
}
|
||||
|
||||
length = length - diff;
|
||||
p = (void*)((uintptr_t)p + (diff << GRANULARITY_BITS));
|
||||
} while (length > 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
64
src/mem/remoteallocator.h
Normal file
64
src/mem/remoteallocator.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/mpscq.h"
|
||||
#include "../mem/allocconfig.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
struct Remote
|
||||
{
|
||||
static const uint64_t SIZECLASS_SHIFT = 56;
|
||||
static const uint64_t SIZECLASS_MASK = 0xffULL << SIZECLASS_SHIFT;
|
||||
static const uint64_t TARGET_MASK = ~SIZECLASS_MASK;
|
||||
|
||||
static_assert(SIZECLASS_MASK == 0xff00'0000'0000'0000ULL);
|
||||
|
||||
using alloc_id_t = size_t;
|
||||
union
|
||||
{
|
||||
std::atomic<Remote*> next;
|
||||
Remote* non_atomic_next;
|
||||
};
|
||||
|
||||
uint64_t value;
|
||||
|
||||
void set_target_id(alloc_id_t id)
|
||||
{
|
||||
assert(id == (id & TARGET_MASK));
|
||||
value = (id & TARGET_MASK) | (value & SIZECLASS_MASK);
|
||||
}
|
||||
|
||||
void set_sizeclass(uint8_t sizeclass)
|
||||
{
|
||||
value = (value & TARGET_MASK) |
|
||||
((static_cast<uint64_t>(sizeclass) << SIZECLASS_SHIFT) &
|
||||
SIZECLASS_MASK);
|
||||
}
|
||||
|
||||
alloc_id_t target_id()
|
||||
{
|
||||
return value & TARGET_MASK;
|
||||
}
|
||||
|
||||
uint8_t sizeclass()
|
||||
{
|
||||
return (value & SIZECLASS_MASK) >> SIZECLASS_SHIFT;
|
||||
}
|
||||
};
|
||||
|
||||
struct RemoteAllocator
|
||||
{
|
||||
using alloc_id_t = Remote::alloc_id_t;
|
||||
// Store the message queue on a separate cacheline. It is mutable data that
|
||||
// is read by other threads.
|
||||
alignas(CACHELINE_SIZE) MPSCQ<Remote> message_queue;
|
||||
|
||||
alloc_id_t id()
|
||||
{
|
||||
return static_cast<alloc_id_t>(
|
||||
reinterpret_cast<uintptr_t>(&message_queue));
|
||||
}
|
||||
};
|
||||
}
|
||||
148
src/mem/sizeclass.h
Normal file
148
src/mem/sizeclass.h
Normal file
@@ -0,0 +1,148 @@
|
||||
#pragma once
|
||||
|
||||
#include "allocconfig.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
constexpr static uint16_t get_slab_offset(uint8_t sc, bool is_short);
|
||||
constexpr static size_t sizeclass_to_size(uint8_t sizeclass);
|
||||
constexpr static uint16_t medium_slab_free(uint8_t sizeclass);
|
||||
|
||||
static inline uint8_t size_to_sizeclass(size_t size)
|
||||
{
|
||||
// Don't use sizeclasses that are not a multiple of the alignment.
|
||||
// For example, 24 byte allocations can be
|
||||
// problematic for some data due to alignment issues.
|
||||
return (uint8_t)bits::to_exp_mant<INTERMEDIATE_BITS, MIN_ALLOC_BITS>(size);
|
||||
}
|
||||
|
||||
constexpr static inline uint8_t size_to_sizeclass_const(size_t size)
|
||||
{
|
||||
// Don't use sizeclasses that are not a multiple of the alignment.
|
||||
// For example, 24 byte allocations can be
|
||||
// problematic for some data due to alignment issues.
|
||||
return (uint8_t)bits::to_exp_mant_const<INTERMEDIATE_BITS, MIN_ALLOC_BITS>(
|
||||
size);
|
||||
}
|
||||
|
||||
constexpr static inline size_t large_sizeclass_to_size(uint8_t large_class)
|
||||
{
|
||||
return (size_t)1 << (large_class + SUPERSLAB_BITS);
|
||||
}
|
||||
|
||||
// Small classes range from [MIN, SLAB], i.e. inclusive.
|
||||
static constexpr size_t NUM_SMALL_CLASSES =
|
||||
size_to_sizeclass_const((size_t)1 << SLAB_BITS) + 1;
|
||||
|
||||
static constexpr size_t NUM_SIZECLASSES =
|
||||
size_to_sizeclass_const((size_t)1 << SUPERSLAB_BITS);
|
||||
|
||||
// Medium classes range from (SLAB, SUPERSLAB), i.e. non-inclusive.
|
||||
static constexpr size_t NUM_MEDIUM_CLASSES =
|
||||
NUM_SIZECLASSES - NUM_SMALL_CLASSES;
|
||||
|
||||
// Large classes range from [SUPERSLAB, ADDRESS_SPACE).
|
||||
static constexpr size_t NUM_LARGE_CLASSES =
|
||||
bits::ADDRESS_BITS - SUPERSLAB_BITS;
|
||||
|
||||
template<size_t X, size_t Y>
|
||||
constexpr void check_same()
|
||||
{
|
||||
static_assert(X == Y, "Values must be the same");
|
||||
}
|
||||
|
||||
static_assert(size_to_sizeclass_const(SUPERSLAB_SIZE) == NUM_SIZECLASSES);
|
||||
|
||||
inline static size_t round_by_sizeclass(size_t rsize, size_t offset)
|
||||
{
|
||||
// check_same<NUM_LARGE_CLASSES, Globals::num_large_classes>();
|
||||
// Must be called with a rounded size.
|
||||
assert(sizeclass_to_size(size_to_sizeclass(rsize)) == rsize);
|
||||
// Only works up to certain offsets, exhaustively tested upto
|
||||
// SUPERSLAB_SIZE.
|
||||
assert(offset <= SUPERSLAB_SIZE);
|
||||
|
||||
size_t align = bits::ctz(rsize);
|
||||
size_t divider = rsize >> align;
|
||||
// Maximum of 24 bits for 16MiB super/medium slab
|
||||
if (INTERMEDIATE_BITS == 0 || divider == 1)
|
||||
{
|
||||
assert(divider == 1);
|
||||
return offset & ~(rsize - 1);
|
||||
}
|
||||
|
||||
if constexpr (bits::is64() && INTERMEDIATE_BITS <= 2)
|
||||
{
|
||||
// Only works for 64 bit multiplication, as the following will overflow in
|
||||
// 32bit.
|
||||
// The code is using reciprocal division, with a shift of 26 bits, this
|
||||
// is considerably more bits than we need in the result. If SUPERSLABS
|
||||
// get larger then we should review this code.
|
||||
static_assert(SUPERSLAB_BITS <= 24, "The following code assumes 24 bits");
|
||||
static constexpr size_t shift = 26;
|
||||
size_t back_shift = shift + align;
|
||||
static constexpr size_t mul_shift = 1ULL << shift;
|
||||
static constexpr uint32_t constants[8] = {0,
|
||||
mul_shift,
|
||||
0,
|
||||
(mul_shift / 3) + 1,
|
||||
0,
|
||||
(mul_shift / 5) + 1,
|
||||
0,
|
||||
(mul_shift / 7) + 1};
|
||||
return ((constants[divider] * offset) >> back_shift) * rsize;
|
||||
}
|
||||
else
|
||||
// Use 32-bit division as considerably faster than 64-bit, and
|
||||
// everything fits into 32bits here.
|
||||
return (uint32_t)(offset / rsize) * rsize;
|
||||
}
|
||||
|
||||
inline static bool is_multiple_of_sizeclass(size_t rsize, size_t offset)
|
||||
{
|
||||
// Must be called with a rounded size.
|
||||
assert(sizeclass_to_size(size_to_sizeclass(rsize)) == rsize);
|
||||
// Only works up to certain offsets, exhaustively tested upto
|
||||
// SUPERSLAB_SIZE.
|
||||
assert(offset <= SUPERSLAB_SIZE);
|
||||
|
||||
size_t align = bits::ctz(rsize);
|
||||
size_t divider = rsize >> align;
|
||||
// Maximum of 24 bits for 16MiB super/medium slab
|
||||
if (INTERMEDIATE_BITS == 0 || divider == 1)
|
||||
{
|
||||
assert(divider == 1);
|
||||
return (offset & (rsize - 1)) == 0;
|
||||
}
|
||||
|
||||
if constexpr (bits::is64() && INTERMEDIATE_BITS <= 2)
|
||||
{
|
||||
// Only works for 64 bit multiplication, as the following will overflow in
|
||||
// 32bit.
|
||||
// The code is using reciprocal division, with a shift of 26 bits, this
|
||||
// is considerably more bits than we need in the result. If SUPERSLABS
|
||||
// get larger then we should review this code.
|
||||
static_assert(SUPERSLAB_BITS <= 24, "The following code assumes 24 bits");
|
||||
static constexpr size_t shift = 31;
|
||||
static constexpr size_t mul_shift = 1ULL << shift;
|
||||
static constexpr uint32_t constants[8] = {0,
|
||||
mul_shift,
|
||||
0,
|
||||
(mul_shift / 3) + 1,
|
||||
0,
|
||||
(mul_shift / 5) + 1,
|
||||
0,
|
||||
(mul_shift / 7) + 1};
|
||||
|
||||
// There is a long chain of zeros after the backshift
|
||||
// However, not all zero so just check a range.
|
||||
// This is exhaustively tested for the current use case
|
||||
return (((constants[divider] * offset)) &
|
||||
(((1ULL << (align + 3)) - 1) << (shift - 3))) == 0;
|
||||
}
|
||||
else
|
||||
// Use 32-bit division as considerably faster than 64-bit, and
|
||||
// everything fits into 32bits here.
|
||||
return (uint32_t)(offset % rsize) == 0;
|
||||
}
|
||||
};
|
||||
71
src/mem/sizeclasstable.h
Normal file
71
src/mem/sizeclasstable.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "superslab.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
struct SizeClassTable
|
||||
{
|
||||
size_t size[NUM_SIZECLASSES];
|
||||
uint16_t bump_ptr_start[NUM_SMALL_CLASSES];
|
||||
uint16_t short_bump_ptr_start[NUM_SMALL_CLASSES];
|
||||
uint16_t count_per_slab[NUM_SMALL_CLASSES];
|
||||
uint16_t medium_slab_slots[NUM_MEDIUM_CLASSES];
|
||||
|
||||
constexpr SizeClassTable()
|
||||
: size(),
|
||||
bump_ptr_start(),
|
||||
short_bump_ptr_start(),
|
||||
count_per_slab(),
|
||||
medium_slab_slots()
|
||||
{
|
||||
for (uint8_t sizeclass = 0; sizeclass < NUM_SIZECLASSES; sizeclass++)
|
||||
{
|
||||
size[sizeclass] =
|
||||
bits::from_exp_mant<INTERMEDIATE_BITS, MIN_ALLOC_BITS>(sizeclass);
|
||||
}
|
||||
|
||||
size_t header_size = sizeof(Superslab);
|
||||
size_t short_slab_size = SLAB_SIZE - header_size;
|
||||
|
||||
for (uint8_t i = 0; i < NUM_SMALL_CLASSES; i++)
|
||||
{
|
||||
short_bump_ptr_start[i] =
|
||||
(uint16_t)(1 + (short_slab_size % size[i]) + header_size);
|
||||
bump_ptr_start[i] = (uint16_t)(1 + (SLAB_SIZE % size[i]));
|
||||
count_per_slab[i] = (uint16_t)(SLAB_SIZE / size[i]);
|
||||
}
|
||||
|
||||
for (uint8_t i = NUM_SMALL_CLASSES; i < NUM_SIZECLASSES; i++)
|
||||
{
|
||||
medium_slab_slots[i - NUM_SMALL_CLASSES] =
|
||||
(uint16_t)((SUPERSLAB_SIZE - Mediumslab::header_size()) / size[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr SizeClassTable sizeclass_metadata = SizeClassTable();
|
||||
|
||||
static inline constexpr uint16_t get_slab_offset(uint8_t sc, bool is_short)
|
||||
{
|
||||
if (is_short)
|
||||
return sizeclass_metadata.short_bump_ptr_start[sc];
|
||||
else
|
||||
return sizeclass_metadata.bump_ptr_start[sc];
|
||||
}
|
||||
|
||||
constexpr static inline size_t sizeclass_to_size(uint8_t sizeclass)
|
||||
{
|
||||
return sizeclass_metadata.size[sizeclass];
|
||||
}
|
||||
|
||||
constexpr static inline size_t sizeclass_to_count(uint8_t sizeclass)
|
||||
{
|
||||
return sizeclass_metadata.count_per_slab[sizeclass];
|
||||
}
|
||||
|
||||
constexpr static inline uint16_t medium_slab_free(uint8_t sizeclass)
|
||||
{
|
||||
return sizeclass_metadata.medium_slab_slots[sizeclass - NUM_SMALL_CLASSES];
|
||||
}
|
||||
}
|
||||
165
src/mem/slab.h
Normal file
165
src/mem/slab.h
Normal file
@@ -0,0 +1,165 @@
|
||||
#pragma once
|
||||
|
||||
#include "superslab.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class Slab
|
||||
{
|
||||
private:
|
||||
uint16_t pointer_to_index(void* p)
|
||||
{
|
||||
// Get the offset from the slab for a memory location.
|
||||
return (uint16_t)((size_t)p - (size_t)this);
|
||||
}
|
||||
|
||||
public:
|
||||
static Slab* get(void* p)
|
||||
{
|
||||
return (Slab*)((size_t)p & SLAB_MASK);
|
||||
}
|
||||
|
||||
Metaslab* get_meta()
|
||||
{
|
||||
Superslab* super = Superslab::get(this);
|
||||
return super->get_meta(this);
|
||||
}
|
||||
|
||||
SlabLink* get_link()
|
||||
{
|
||||
return get_meta()->get_link(this);
|
||||
}
|
||||
|
||||
template<ZeroMem zero_mem, typename MemoryProvider>
|
||||
void* alloc(SlabList* sc, size_t rsize, MemoryProvider& memory_provider)
|
||||
{
|
||||
// Read the head from the metadata stored in the superslab.
|
||||
Metaslab* meta = get_meta();
|
||||
uint16_t head = meta->head;
|
||||
|
||||
assert(rsize == sizeclass_to_size(meta->sizeclass));
|
||||
meta->debug_slab_invariant(is_short(), this);
|
||||
assert(sc->get_head() == (SlabLink*)((size_t)this + meta->link));
|
||||
assert(!meta->is_full());
|
||||
|
||||
meta->add_use();
|
||||
|
||||
void* p;
|
||||
|
||||
if ((head & 1) == 0)
|
||||
{
|
||||
p = (void*)((size_t)this + head);
|
||||
|
||||
// Read the next slot from the memory that's about to be allocated.
|
||||
uint16_t next = *(uint16_t*)p;
|
||||
meta->head = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This slab is being bump allocated.
|
||||
p = (void*)((size_t)this + head - 1);
|
||||
meta->head = head + (uint16_t)rsize;
|
||||
if (meta->head == 1)
|
||||
{
|
||||
meta->set_full();
|
||||
}
|
||||
}
|
||||
|
||||
// If we're full, we're no longer the current slab for this sizeclass
|
||||
if (meta->is_full())
|
||||
sc->pop();
|
||||
|
||||
meta->debug_slab_invariant(is_short(), this);
|
||||
|
||||
if (zero_mem == YesZero)
|
||||
{
|
||||
if (rsize < PAGE_ALIGNED_SIZE)
|
||||
memory_provider.zero(p, rsize);
|
||||
else
|
||||
memory_provider.template zero<true>(p, rsize);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// Returns true, if it alters get_status.
|
||||
template<typename MemoryProvider>
|
||||
inline typename Superslab::Action dealloc(
|
||||
SlabList* sc, Superslab* super, void* p, MemoryProvider& memory_provider)
|
||||
{
|
||||
Metaslab* meta = super->get_meta(this);
|
||||
|
||||
bool was_full = meta->is_full();
|
||||
meta->debug_slab_invariant(is_short(), this);
|
||||
meta->sub_use();
|
||||
|
||||
#ifndef SNMALLOC_SAFE_CLIENT
|
||||
if (!is_multiple_of_sizeclass(
|
||||
sizeclass_to_size(meta->sizeclass),
|
||||
(uintptr_t)this + SLAB_SIZE - (uintptr_t)p))
|
||||
{
|
||||
error("Not deallocating start of an object");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (was_full)
|
||||
{
|
||||
// We are not on the sizeclass list.
|
||||
if (!meta->is_unused())
|
||||
{
|
||||
// Update the head and the sizeclass link.
|
||||
uint16_t index = pointer_to_index(p);
|
||||
meta->head = index;
|
||||
assert(meta->valid_head(is_short()));
|
||||
meta->link = index;
|
||||
|
||||
// Push on the list of slabs for this sizeclass.
|
||||
sc->insert(meta->get_link(this));
|
||||
meta->debug_slab_invariant(is_short(), this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dealloc on the superslab.
|
||||
if (is_short())
|
||||
return super->dealloc_short_slab(memory_provider);
|
||||
else
|
||||
return super->dealloc_slab(this, memory_provider);
|
||||
}
|
||||
}
|
||||
else if (meta->is_unused())
|
||||
{
|
||||
// Remove from the sizeclass list and dealloc on the superslab.
|
||||
sc->remove(meta->get_link(this));
|
||||
|
||||
if (is_short())
|
||||
return super->dealloc_short_slab(memory_provider);
|
||||
else
|
||||
return super->dealloc_slab(this, memory_provider);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
sc->debug_check_contains(meta->get_link(this));
|
||||
#endif
|
||||
|
||||
// Update the head and the next pointer in the free list.
|
||||
uint16_t head = meta->head;
|
||||
uint16_t current = pointer_to_index(p);
|
||||
|
||||
// Set the head to the memory being deallocated.
|
||||
meta->head = current;
|
||||
assert(meta->valid_head(is_short()));
|
||||
|
||||
// Set the next pointer to the previous head.
|
||||
*(uint16_t*)p = head;
|
||||
meta->debug_slab_invariant(is_short(), this);
|
||||
}
|
||||
return Superslab::NoSlabReturn;
|
||||
}
|
||||
|
||||
bool is_short()
|
||||
{
|
||||
return ((size_t)this & SUPERSLAB_MASK) == (size_t)this;
|
||||
}
|
||||
};
|
||||
}
|
||||
233
src/mem/superslab.h
Normal file
233
src/mem/superslab.h
Normal file
@@ -0,0 +1,233 @@
|
||||
#pragma once
|
||||
|
||||
#include "metaslab.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class Superslab : public Allocslab
|
||||
{
|
||||
// This is the view of a 16 mb superslab when it is being used to allocate
|
||||
// 64 kb slabs.
|
||||
private:
|
||||
friend DLList<Superslab>;
|
||||
|
||||
// Keep the allocator pointer on a separate cache line. It is read by
|
||||
// other threads, and does not change, so we avoid false sharing.
|
||||
alignas(CACHELINE_SIZE)
|
||||
// The superslab is kept on a doubly linked list of superslabs which
|
||||
// have some space.
|
||||
Superslab* next;
|
||||
Superslab* prev;
|
||||
|
||||
// This is a reference to the first unused slab in the free slab list
|
||||
// It is does not contain the short slab, which is handled using a bit
|
||||
// in the "used" field below. The list is terminated by pointing to
|
||||
// the short slab.
|
||||
// The head linked list has an absolute pointer for head, but the next
|
||||
// pointers stores in the metaslabs are relative pointers, that is they
|
||||
// are the relative offset to the next entry minus 1. This means that
|
||||
// all zeros is a list that chains through all the blocks, so the zero
|
||||
// initialised memory requires no more work.
|
||||
uint8_t head;
|
||||
|
||||
// Represents twice the number of full size slabs used
|
||||
// plus 1 for the short slab. i.e. using 3 slabs and the
|
||||
// short slab would be 6 + 1 = 7
|
||||
uint16_t used;
|
||||
|
||||
Metaslab meta[SLAB_COUNT];
|
||||
|
||||
// Used size_t as results in better code in MSVC
|
||||
size_t slab_to_index(Slab* slab)
|
||||
{
|
||||
auto res = (((size_t)slab - (size_t)this) >> SLAB_BITS);
|
||||
assert(res == (uint8_t)res);
|
||||
return res;
|
||||
}
|
||||
|
||||
public:
|
||||
enum Status
|
||||
{
|
||||
Full,
|
||||
Available,
|
||||
OnlyShortSlabAvailable,
|
||||
Empty
|
||||
};
|
||||
|
||||
enum Action
|
||||
{
|
||||
NoSlabReturn = 0,
|
||||
NoStatusChange = 1,
|
||||
StatusChange = 2
|
||||
};
|
||||
|
||||
static Superslab* get(void* p)
|
||||
{
|
||||
return (Superslab*)((size_t)p & SUPERSLAB_MASK);
|
||||
}
|
||||
|
||||
static bool is_short_sizeclass(uint8_t sizeclass)
|
||||
{
|
||||
constexpr uint8_t h = size_to_sizeclass_const(sizeof(Superslab));
|
||||
return sizeclass <= h;
|
||||
}
|
||||
|
||||
template<typename MemoryProvider>
|
||||
void init(RemoteAllocator* alloc, MemoryProvider& memory_provider)
|
||||
{
|
||||
allocator = alloc;
|
||||
|
||||
if (kind != Super)
|
||||
{
|
||||
// If this wasn't previously a Superslab, we need to set up the
|
||||
// header.
|
||||
kind = Super;
|
||||
// Point head at the first non-short slab.
|
||||
head = 1;
|
||||
|
||||
if (kind != Fresh)
|
||||
{
|
||||
// If this wasn't previously Fresh, we need to zero some things.
|
||||
used = 0;
|
||||
memory_provider.zero(meta, SLAB_COUNT * sizeof(Metaslab));
|
||||
}
|
||||
|
||||
meta[0].set_unused();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_empty()
|
||||
{
|
||||
return used == 0;
|
||||
}
|
||||
|
||||
bool is_full()
|
||||
{
|
||||
return (used == (((SLAB_COUNT - 1) << 1) + 1));
|
||||
}
|
||||
|
||||
bool is_almost_full()
|
||||
{
|
||||
return (used >= ((SLAB_COUNT - 1) << 1));
|
||||
}
|
||||
|
||||
Status get_status()
|
||||
{
|
||||
if (!is_almost_full())
|
||||
{
|
||||
if (!is_empty())
|
||||
{
|
||||
return Available;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_full())
|
||||
{
|
||||
return OnlyShortSlabAvailable;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Metaslab* get_meta(Slab* slab)
|
||||
{
|
||||
return &meta[slab_to_index(slab)];
|
||||
}
|
||||
|
||||
template<typename MemoryProvider>
|
||||
Slab* alloc_short_slab(uint8_t sizeclass, MemoryProvider& memory_provider)
|
||||
{
|
||||
if ((used & 1) == 1)
|
||||
return alloc_slab(sizeclass, memory_provider);
|
||||
|
||||
meta[0].head = get_slab_offset(sizeclass, true);
|
||||
meta[0].sizeclass = sizeclass;
|
||||
meta[0].link = SLABLINK_INDEX;
|
||||
|
||||
if (decommit_strategy == DecommitAll)
|
||||
{
|
||||
memory_provider.template notify_using<NoZero>(
|
||||
(void*)((size_t)this + OS_PAGE_SIZE), SLAB_SIZE - OS_PAGE_SIZE);
|
||||
}
|
||||
|
||||
used++;
|
||||
return (Slab*)this;
|
||||
}
|
||||
|
||||
template<typename MemoryProvider>
|
||||
Slab* alloc_slab(uint8_t sizeclass, MemoryProvider& memory_provider)
|
||||
{
|
||||
Slab* slab = (Slab*)((size_t)this + ((size_t)head << SLAB_BITS));
|
||||
|
||||
uint8_t n = meta[head].next;
|
||||
|
||||
meta[head].head = get_slab_offset(sizeclass, false);
|
||||
meta[head].sizeclass = sizeclass;
|
||||
meta[head].link = SLABLINK_INDEX;
|
||||
|
||||
head = head + n + 1;
|
||||
used += 2;
|
||||
|
||||
if (decommit_strategy == DecommitAll)
|
||||
{
|
||||
memory_provider.template notify_using<NoZero>(slab, SLAB_SIZE);
|
||||
}
|
||||
|
||||
return slab;
|
||||
}
|
||||
|
||||
// Returns true, if this alters the value of get_status
|
||||
template<typename MemoryProvider>
|
||||
Action dealloc_slab(Slab* slab, MemoryProvider& memory_provider)
|
||||
{
|
||||
// This is not the short slab.
|
||||
uint8_t index = (uint8_t)slab_to_index(slab);
|
||||
uint8_t n = head - index - 1;
|
||||
|
||||
meta[index].next = n;
|
||||
head = index;
|
||||
bool was_almost_full = is_almost_full();
|
||||
used -= 2;
|
||||
|
||||
if (decommit_strategy == DecommitAll)
|
||||
memory_provider.notify_not_using(slab, SLAB_SIZE);
|
||||
|
||||
assert(meta[index].is_unused());
|
||||
if (was_almost_full || is_empty())
|
||||
return StatusChange;
|
||||
|
||||
return NoStatusChange;
|
||||
}
|
||||
|
||||
// Returns true, if this alters the value of get_status
|
||||
template<typename MemoryProvider>
|
||||
Action dealloc_short_slab(MemoryProvider& memory_provider)
|
||||
{
|
||||
// This is the short slab.
|
||||
if (decommit_strategy == DecommitAll)
|
||||
{
|
||||
memory_provider.notify_not_using(
|
||||
(void*)((size_t)this + OS_PAGE_SIZE), SLAB_SIZE - OS_PAGE_SIZE);
|
||||
}
|
||||
|
||||
bool was_full = is_full();
|
||||
used--;
|
||||
|
||||
assert(meta[0].is_unused());
|
||||
if (was_full || is_empty())
|
||||
return StatusChange;
|
||||
|
||||
return NoStatusChange;
|
||||
}
|
||||
};
|
||||
}
|
||||
265
src/mem/threadalloc.h
Normal file
265
src/mem/threadalloc.h
Normal file
@@ -0,0 +1,265 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/helpers.h"
|
||||
#include "globalalloc.h"
|
||||
#if defined(SNMALLOC_USE_THREAD_DESTRUCTOR) && \
|
||||
defined(SNMALLOC_USE_THREAD_CLEANUP)
|
||||
#error At most one out of SNMALLOC_USE_THREAD_CLEANUP and SNMALLOC_USE_THREAD_DESTRUCTOR may be defined.
|
||||
#endif
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
extern "C" void _malloc_thread_cleanup(void);
|
||||
|
||||
#ifdef SNMALLOC_EXTERNAL_THREAD_ALLOC
|
||||
/**
|
||||
* Version of the `ThreadAlloc` interface that does no managment of thread
|
||||
* local state, and just assumes that "ThreadAllocUntyped::get" has been
|
||||
* declared before including snmalloc.h. As it is included before, it cannot
|
||||
* know the allocator type, hence the casting.
|
||||
*
|
||||
* This class is used only when snmalloc is compiled as part of a runtime,
|
||||
* which has its own managment of the thread local allocator pointer.
|
||||
*/
|
||||
class ThreadAllocUntypedWrapper
|
||||
{
|
||||
public:
|
||||
static inline Alloc*& get()
|
||||
{
|
||||
return (Alloc*&)ThreadAllocUntyped::get();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Version of the `ThreadAlloc` interface that uses a hook provided by libc
|
||||
* to destroy thread-local state. This is the ideal option, because it
|
||||
* enforces ordering of destruction such that the malloc state is destroyed
|
||||
* after anything that can allocate memory.
|
||||
*
|
||||
* This class is used only when snmalloc is compiled as part of a compatible
|
||||
* libc (for example, FreeBSD libc).
|
||||
*/
|
||||
class ThreadAllocLibcCleanup
|
||||
{
|
||||
/**
|
||||
* Libc will call `_malloc_thread_cleanup` just before a thread terminates.
|
||||
* This function must be allowed to call back into this class to destroy
|
||||
* the state.
|
||||
*/
|
||||
friend void _malloc_thread_cleanup(void);
|
||||
|
||||
/**
|
||||
* Function called when the thread exits. This is guaranteed to be called
|
||||
* precisely once per thread and releases the current allocator.
|
||||
*/
|
||||
static inline void exit()
|
||||
{
|
||||
if (auto* per_thread = get(false))
|
||||
{
|
||||
current_alloc_pool()->release(per_thread);
|
||||
per_thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns a pointer to the allocator associated with this thread. If
|
||||
* `create` is true, it will create an allocator if one does not exist,
|
||||
* otherwise it will return `nullptr` in this case. This should be called
|
||||
* with `create == false` only during thread teardown.
|
||||
*
|
||||
* The non-create case exists so that the `per_thread` variable can be a
|
||||
* local static and not a global, allowing ODR to deduplicate it.
|
||||
*/
|
||||
static inline Alloc*& get(bool create = true)
|
||||
{
|
||||
static thread_local Alloc* per_thread;
|
||||
if (!per_thread && create)
|
||||
{
|
||||
per_thread = current_alloc_pool()->acquire();
|
||||
}
|
||||
return per_thread;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Version of the `ThreadAlloc` interface that uses C++ `thread_local`
|
||||
* destructors for cleanup. If a per-thread allocator is used during the
|
||||
* destruction of other per-thread data, this class will create a new
|
||||
* instance and register its destructor, so should eventually result in
|
||||
* cleanup, but may result in allocators being returned to the global pool
|
||||
* and then reacquired multiple times.
|
||||
*
|
||||
* This implementation depends on nothing outside of a working C++
|
||||
* environment and so should be the simplest for initial bringup on an
|
||||
* unsupported platform. It is currently used in the FreeBSD kernel version.
|
||||
*/
|
||||
class ThreadAllocThreadDestructor
|
||||
{
|
||||
/**
|
||||
* A pointer to the allocator owned by this thread.
|
||||
*/
|
||||
Alloc* alloc;
|
||||
|
||||
/**
|
||||
* Constructor. Acquires a new allocator and associates it with this
|
||||
* object. There should be only one instance of this class per thread.
|
||||
*/
|
||||
ThreadAllocThreadDestructor() : alloc(current_alloc_pool()->acquire()) {}
|
||||
|
||||
/**
|
||||
* Destructor. Releases the allocator owned by this thread.
|
||||
*/
|
||||
~ThreadAllocThreadDestructor()
|
||||
{
|
||||
current_alloc_pool()->release(alloc);
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Public interface, returns the allocator for this thread, constructing
|
||||
* one if necessary.
|
||||
*/
|
||||
static inline Alloc*& get()
|
||||
{
|
||||
static thread_local ThreadAllocThreadDestructor per_thread;
|
||||
return per_thread.alloc;
|
||||
}
|
||||
};
|
||||
// When targeting the FreeBSD kernel, the pthread header exists, but the
|
||||
// pthread symbols do not, so don't compile this because it will fail to
|
||||
// link.
|
||||
#ifndef FreeBSD_KERNEL
|
||||
/**
|
||||
* Version of the `ThreadAlloc` interface that uses thread-specific (POSIX
|
||||
* threads) or Fiber-local (Windows) storage with an explicit destructor.
|
||||
* Neither of the underlying mechanisms guarantee ordering, so the cleanup
|
||||
* may be called before other cleanup functions or thread-local destructors.
|
||||
*
|
||||
* This implementation is used when using snmalloc as a library
|
||||
* implementation of malloc, but not embedding it in C standard library.
|
||||
* Using this implementation removes the dependency on a C++ runtime library.
|
||||
*/
|
||||
class ThreadAllocExplicitTLSCleanup
|
||||
{
|
||||
/**
|
||||
* Cleanup function. This is registered with the operating system's
|
||||
* thread- or fibre-local storage subsystem to clean up the per-thread
|
||||
* allocator.
|
||||
*/
|
||||
static inline void
|
||||
# ifdef _WIN32
|
||||
NTAPI
|
||||
# endif
|
||||
thread_alloc_release(void* p)
|
||||
{
|
||||
Alloc** pp = (Alloc**)p;
|
||||
current_alloc_pool()->release(*pp);
|
||||
*pp = nullptr;
|
||||
}
|
||||
|
||||
# ifdef _WIN32
|
||||
/**
|
||||
* Key type used to identify fibre-local storage.
|
||||
*/
|
||||
using tls_key_t = DWORD;
|
||||
|
||||
/**
|
||||
* On Windows, construct a new fibre-local storage allocation. This
|
||||
* function must not be called more than once.
|
||||
*/
|
||||
static inline tls_key_t tls_key_create() noexcept
|
||||
{
|
||||
return FlsAlloc(thread_alloc_release);
|
||||
}
|
||||
|
||||
/**
|
||||
* On Windows, store a pointer to a `thread_local` pointer to an allocator
|
||||
* into fibre-local storage. This function takes a pointer to the
|
||||
* `thread_local` allocation, rather than to the pointee, so that the
|
||||
* cleanup function can zero the pointer.
|
||||
*
|
||||
* This must not be called until after `tls_key_create` has returned.
|
||||
*/
|
||||
static inline void tls_set_value(tls_key_t key, Alloc** value)
|
||||
{
|
||||
FlsSetValue(key, static_cast<void*>(value));
|
||||
}
|
||||
# else
|
||||
/**
|
||||
* Key type used for thread-specific storage.
|
||||
*/
|
||||
using tls_key_t = pthread_key_t;
|
||||
|
||||
/**
|
||||
* On POSIX systems, construct a new thread-specific storage allocation.
|
||||
* This function must not be called more than once.
|
||||
*/
|
||||
static inline tls_key_t tls_key_create() noexcept
|
||||
{
|
||||
tls_key_t key;
|
||||
pthread_key_create(&key, thread_alloc_release);
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* On POSIX systems, store a pointer to a `thread_local` pointer to an
|
||||
* allocator into fibre-local storage. This function takes a pointer to
|
||||
* the `thread_local` allocation, rather than to the pointee, so that the
|
||||
* cleanup function can zero the pointer.
|
||||
*
|
||||
* This must not be called until after `tls_key_create` has returned.
|
||||
*/
|
||||
static inline void tls_set_value(tls_key_t key, Alloc** value)
|
||||
{
|
||||
pthread_setspecific(key, static_cast<void*>(value));
|
||||
}
|
||||
# endif
|
||||
public:
|
||||
/**
|
||||
* Public interface, returns the allocator for the current thread,
|
||||
* constructing it if necessary.
|
||||
*/
|
||||
static inline Alloc*& get()
|
||||
{
|
||||
static thread_local Alloc* per_thread;
|
||||
|
||||
// If we don't have an allocator, construct one.
|
||||
if (!per_thread)
|
||||
{
|
||||
// Construct the allocator and assign it to `per_thread` *before* doing
|
||||
// anything else. This is important because `tls_key_create` may
|
||||
// allocate memory and if we are providing the `malloc` implementation
|
||||
// then this function must be re-entrant within a single thread. In
|
||||
// this case, the second call to this function will simply return the
|
||||
// allocator.
|
||||
per_thread = current_alloc_pool()->acquire();
|
||||
|
||||
tls_key_t key = Singleton<tls_key_t, tls_key_create>::get();
|
||||
// Associate the new allocator with the destructor.
|
||||
tls_set_value(key, &per_thread);
|
||||
}
|
||||
|
||||
return per_thread;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef SNMALLOC_USE_THREAD_CLEANUP
|
||||
/**
|
||||
* Entry point the allows libc to call into the allocator for per-thread
|
||||
* cleanup.
|
||||
*/
|
||||
extern "C" void _malloc_thread_cleanup(void)
|
||||
{
|
||||
ThreadAllocLibcCleanup::exit();
|
||||
}
|
||||
using ThreadAlloc = ThreadAllocLibcCleanup;
|
||||
#elif defined(SNMALLOC_USE_THREAD_DESTRUCTOR)
|
||||
using ThreadAlloc = ThreadAllocThreadDestructor;
|
||||
#elif defined(SNMALLOC_EXTERNAL_THREAD_ALLOC)
|
||||
using ThreadAlloc = ThreadAllocUntypedWrapper;
|
||||
#else
|
||||
using ThreadAlloc = ThreadAllocExplicitTLSCleanup;
|
||||
#endif
|
||||
}
|
||||
86
src/mem/typealloc.h
Normal file
86
src/mem/typealloc.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/flaglock.h"
|
||||
#include "../ds/mpmcstack.h"
|
||||
#include "typeallocated.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<class T, class MemoryProvider = GlobalVirtual>
|
||||
class TypeAlloc
|
||||
{
|
||||
private:
|
||||
friend TypeAllocated<T>;
|
||||
|
||||
std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||
MPMCStack<T, PreZeroed> stack;
|
||||
T* list = nullptr;
|
||||
|
||||
TypeAlloc(MemoryProvider& m) : memory_provider(m) {}
|
||||
|
||||
public:
|
||||
MemoryProvider& memory_provider;
|
||||
|
||||
static TypeAlloc* make(MemoryProvider& memory_provider) noexcept
|
||||
{
|
||||
auto r = memory_provider.alloc_chunk(sizeof(TypeAlloc));
|
||||
return new (r) TypeAlloc(memory_provider);
|
||||
}
|
||||
|
||||
static TypeAlloc* make() noexcept
|
||||
{
|
||||
return make(default_memory_provider);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
T* alloc(Args&&... args)
|
||||
{
|
||||
T* p = stack.pop();
|
||||
|
||||
if (p != nullptr)
|
||||
return p;
|
||||
|
||||
p = (T*)memory_provider.alloc_chunk(sizeof(T));
|
||||
|
||||
new (p) T(std::forward<Args...>(args)...);
|
||||
|
||||
FlagLock f(lock);
|
||||
p->list_next = list;
|
||||
list = p;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void dealloc(T* p)
|
||||
{
|
||||
// The object's destructor is not run. If the object is "reallocated", it
|
||||
// is returned without the constructor being run, so the object is reused
|
||||
// without re-initialisation.
|
||||
stack.push(p);
|
||||
}
|
||||
|
||||
T* extract(T* p = nullptr)
|
||||
{
|
||||
// Returns a linked list of all objects in the stack, emptying the stack.
|
||||
if (p == nullptr)
|
||||
return stack.pop_all();
|
||||
else
|
||||
return p->next;
|
||||
}
|
||||
|
||||
void restore(T* first, T* last)
|
||||
{
|
||||
// Pushes a linked list of objects onto the stack. Use to put a linked
|
||||
// list returned by extract back onto the stack.
|
||||
stack.push(first, last);
|
||||
}
|
||||
|
||||
T* iterate(T* p = nullptr)
|
||||
{
|
||||
if (p == nullptr)
|
||||
return list;
|
||||
else
|
||||
return p->list_next;
|
||||
}
|
||||
};
|
||||
}
|
||||
19
src/mem/typeallocated.h
Normal file
19
src/mem/typeallocated.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/bits.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<class T>
|
||||
class TypeAllocated
|
||||
{
|
||||
private:
|
||||
template<class TT, class MemoryProvider>
|
||||
friend class TypeAlloc;
|
||||
template<class TT, Construction c>
|
||||
friend class MPMCStack;
|
||||
|
||||
std::atomic<T*> next = nullptr;
|
||||
T* list_next;
|
||||
};
|
||||
}
|
||||
188
src/override/malloc.cc
Normal file
188
src/override/malloc.cc
Normal file
@@ -0,0 +1,188 @@
|
||||
#include "../snmalloc.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
#ifndef SNMALLOC_NAME_MANGLE
|
||||
# define SNMALLOC_NAME_MANGLE(a) a
|
||||
#endif
|
||||
|
||||
extern "C"
|
||||
{
|
||||
void* SNMALLOC_NAME_MANGLE(__malloc_end_pointer)(void* ptr)
|
||||
{
|
||||
return Alloc::external_pointer<End>(ptr);
|
||||
}
|
||||
|
||||
void* SNMALLOC_NAME_MANGLE(malloc)(size_t size)
|
||||
{
|
||||
// Include size 0 in the first sizeclass.
|
||||
size = ((size - 1) >> (bits::BITS - 1)) + size;
|
||||
|
||||
return ThreadAlloc::get()->alloc(size);
|
||||
}
|
||||
|
||||
void SNMALLOC_NAME_MANGLE(free)(void* ptr)
|
||||
{
|
||||
if (ptr == nullptr)
|
||||
return;
|
||||
|
||||
ThreadAlloc::get()->dealloc(ptr);
|
||||
}
|
||||
|
||||
void* SNMALLOC_NAME_MANGLE(calloc)(size_t nmemb, size_t size)
|
||||
{
|
||||
bool overflow = false;
|
||||
size_t sz = bits::umul(size, nmemb, overflow);
|
||||
if (overflow)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
// Include size 0 in the first sizeclass.
|
||||
sz = ((sz - 1) >> (bits::BITS - 1)) + sz;
|
||||
return ThreadAlloc::get()->alloc<ZeroMem::YesZero>(sz);
|
||||
}
|
||||
|
||||
size_t SNMALLOC_NAME_MANGLE(malloc_usable_size)(void* ptr)
|
||||
{
|
||||
return Alloc::alloc_size(ptr);
|
||||
}
|
||||
|
||||
void* SNMALLOC_NAME_MANGLE(realloc)(void* ptr, size_t size)
|
||||
{
|
||||
if (size == (size_t)-1)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return nullptr;
|
||||
}
|
||||
if (ptr == nullptr)
|
||||
{
|
||||
return SNMALLOC_NAME_MANGLE(malloc)(size);
|
||||
}
|
||||
if (size == 0)
|
||||
{
|
||||
SNMALLOC_NAME_MANGLE(free)(ptr);
|
||||
return nullptr;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
// This check is redundant, because the check in memcpy will fail if this
|
||||
// is skipped, but it's useful for debugging.
|
||||
if (Alloc::external_pointer<Start>(ptr) != ptr)
|
||||
{
|
||||
error(
|
||||
"Calling realloc on pointer that is not to the start of an allocation");
|
||||
}
|
||||
#endif
|
||||
void* p = SNMALLOC_NAME_MANGLE(malloc)(size);
|
||||
if (p)
|
||||
{
|
||||
assert(p == Alloc::external_pointer<Start>(p));
|
||||
size_t sz =
|
||||
(std::min)(size, SNMALLOC_NAME_MANGLE(malloc_usable_size)(ptr));
|
||||
memcpy(p, ptr, sz);
|
||||
SNMALLOC_NAME_MANGLE(free)(ptr);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
#ifndef __FreeBSD__
|
||||
void* SNMALLOC_NAME_MANGLE(reallocarray)(void* ptr, size_t nmemb, size_t size)
|
||||
{
|
||||
bool overflow = false;
|
||||
size_t sz = bits::umul(size, nmemb, overflow);
|
||||
if (overflow)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return nullptr;
|
||||
}
|
||||
return SNMALLOC_NAME_MANGLE(realloc)(ptr, sz);
|
||||
}
|
||||
#endif
|
||||
|
||||
void* SNMALLOC_NAME_MANGLE(aligned_alloc)(size_t alignment, size_t size)
|
||||
{
|
||||
assert((size % alignment) == 0);
|
||||
(void)alignment;
|
||||
return SNMALLOC_NAME_MANGLE(malloc)(size);
|
||||
}
|
||||
|
||||
void* SNMALLOC_NAME_MANGLE(memalign)(size_t alignment, size_t size)
|
||||
{
|
||||
if (
|
||||
(alignment == 0) || (alignment == size_t(-1)) ||
|
||||
(alignment > SUPERSLAB_SIZE))
|
||||
{
|
||||
errno = EINVAL;
|
||||
return nullptr;
|
||||
}
|
||||
if ((size + alignment) < size)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint8_t sc = size_to_sizeclass((std::max)(size, alignment));
|
||||
if (sc >= NUM_SIZECLASSES)
|
||||
{
|
||||
// large allocs are 16M aligned.
|
||||
return SNMALLOC_NAME_MANGLE(malloc)(size);
|
||||
}
|
||||
for (; sc < NUM_SIZECLASSES; sc++)
|
||||
{
|
||||
size = sizeclass_to_size(sc);
|
||||
if ((size & -size) >= alignment)
|
||||
{
|
||||
return SNMALLOC_NAME_MANGLE(aligned_alloc)(alignment, size);
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int SNMALLOC_NAME_MANGLE(posix_memalign)(
|
||||
void** memptr, size_t alignment, size_t size)
|
||||
{
|
||||
if (
|
||||
((alignment % sizeof(void*)) != 0) ||
|
||||
((alignment & (alignment - 1)) != 0) || (alignment == 0))
|
||||
{
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
void* p = SNMALLOC_NAME_MANGLE(memalign)(alignment, size);
|
||||
if (p == nullptr)
|
||||
{
|
||||
return ENOMEM;
|
||||
}
|
||||
*memptr = p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef __FreeBSD__
|
||||
void* SNMALLOC_NAME_MANGLE(valloc)(size_t size)
|
||||
{
|
||||
return SNMALLOC_NAME_MANGLE(memalign)(OS_PAGE_SIZE, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
void* SNMALLOC_NAME_MANGLE(pvalloc)(size_t size)
|
||||
{
|
||||
if (size == size_t(-1))
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return nullptr;
|
||||
}
|
||||
return SNMALLOC_NAME_MANGLE(memalign)(
|
||||
OS_PAGE_SIZE, (size + OS_PAGE_SIZE - 1) & ~(OS_PAGE_SIZE - 1));
|
||||
}
|
||||
|
||||
void SNMALLOC_NAME_MANGLE(_malloc_prefork)(void) {}
|
||||
void SNMALLOC_NAME_MANGLE(_malloc_postfork)(void) {}
|
||||
void SNMALLOC_NAME_MANGLE(_malloc_first_thread)(void) {}
|
||||
int SNMALLOC_NAME_MANGLE(mallctl)(const char*, void*, size_t*, void*, size_t)
|
||||
{
|
||||
return ENOENT;
|
||||
}
|
||||
}
|
||||
67
src/override/new.cc
Normal file
67
src/override/new.cc
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "../mem/alloc.h"
|
||||
#include "../mem/threadalloc.h"
|
||||
#include "../snmalloc.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# define EXCEPTSPEC
|
||||
#else
|
||||
# ifdef _GLIBCXX_USE_NOEXCEPT
|
||||
# define EXCEPTSPEC _GLIBCXX_USE_NOEXCEPT
|
||||
# elif defined(_NOEXCEPT)
|
||||
# define EXCEPTSPEC _NOEXCEPT
|
||||
# else
|
||||
# define EXCEPTSPEC
|
||||
# endif
|
||||
#endif
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
void* operator new(size_t size)
|
||||
{
|
||||
return ThreadAlloc::get()->alloc(size);
|
||||
}
|
||||
|
||||
void* operator new[](size_t size)
|
||||
{
|
||||
return ThreadAlloc::get()->alloc(size);
|
||||
}
|
||||
|
||||
void* operator new(size_t size, std::nothrow_t&)
|
||||
{
|
||||
return ThreadAlloc::get()->alloc(size);
|
||||
}
|
||||
|
||||
void* operator new[](size_t size, std::nothrow_t&)
|
||||
{
|
||||
return ThreadAlloc::get()->alloc(size);
|
||||
}
|
||||
|
||||
void operator delete(void* p)EXCEPTSPEC
|
||||
{
|
||||
ThreadAlloc::get()->dealloc(p);
|
||||
}
|
||||
|
||||
void operator delete(void* p, size_t size)EXCEPTSPEC
|
||||
{
|
||||
ThreadAlloc::get()->dealloc(p, size);
|
||||
}
|
||||
|
||||
void operator delete(void* p, std::nothrow_t&)
|
||||
{
|
||||
ThreadAlloc::get()->dealloc(p);
|
||||
}
|
||||
|
||||
void operator delete[](void* p) EXCEPTSPEC
|
||||
{
|
||||
ThreadAlloc::get()->dealloc(p);
|
||||
}
|
||||
|
||||
void operator delete[](void* p, size_t size) EXCEPTSPEC
|
||||
{
|
||||
ThreadAlloc::get()->dealloc(p, size);
|
||||
}
|
||||
|
||||
void operator delete[](void* p, std::nothrow_t&)
|
||||
{
|
||||
ThreadAlloc::get()->dealloc(p);
|
||||
}
|
||||
46
src/pal/pal.h
Normal file
46
src/pal/pal.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
void error(const char* const str);
|
||||
}
|
||||
|
||||
// If simultating OE, then we need the underlying platform
|
||||
#if !defined(OPEN_ENCLAVE) || defined(OPEN_ENCLAVE_SIMULATION)
|
||||
# include "pal_free_bsd_kernel.h"
|
||||
# include "pal_freebsd.h"
|
||||
# include "pal_linux.h"
|
||||
# include "pal_windows.h"
|
||||
#endif
|
||||
#include "pal_open_enclave.h"
|
||||
#include "pal_plain.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
#if !defined(OPEN_ENCLAVE) || defined(OPEN_ENCLAVE_SIMULATION)
|
||||
using DefaultPal =
|
||||
# if defined(_WIN32)
|
||||
PALWindows;
|
||||
# elif defined(__linux__)
|
||||
PALLinux;
|
||||
# elif defined(FreeBSD_KERNEL)
|
||||
PALFreeBSDKernel;
|
||||
# elif defined(__FreeBSD__)
|
||||
PALFBSD;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
using Pal =
|
||||
#ifdef OPEN_ENCLAVE
|
||||
PALPlainMixin<PALOpenEnclave>;
|
||||
#elif defined(SNMALLOC_MEMORY_PROVIDER)
|
||||
PALPlainMixin<SNMALLOC_MEMORY_PROVIDER>
|
||||
#else
|
||||
DefaultPal;
|
||||
#endif
|
||||
|
||||
inline void error(const char* const str)
|
||||
{
|
||||
Pal::error(str);
|
||||
}
|
||||
}
|
||||
90
src/pal/pal_free_bsd_kernel.h
Normal file
90
src/pal/pal_free_bsd_kernel.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/bits.h"
|
||||
#include "../mem/allocconfig.h"
|
||||
|
||||
#if defined(FreeBSD_KERNEL)
|
||||
extern "C"
|
||||
{
|
||||
# include <sys/vmem.h>
|
||||
# include <vm/vm.h>
|
||||
# include <vm/vm_extern.h>
|
||||
# include <vm/vm_kern.h>
|
||||
# include <vm/vm_object.h>
|
||||
# include <vm/vm_param.h>
|
||||
}
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class PALFreeBSDKernel
|
||||
{
|
||||
vm_offset_t get_vm_offset(uint_ptr_t p)
|
||||
{
|
||||
return static_cast<vm_offset_t>(reinterpret_cast<uintptr_t>(p));
|
||||
}
|
||||
|
||||
public:
|
||||
void error(const char* const str)
|
||||
{
|
||||
panic("snmalloc error: %s", str);
|
||||
}
|
||||
|
||||
/// Notify platform that we will not be using these pages
|
||||
void notify_not_using(void* p, size_t size)
|
||||
{
|
||||
vm_offset_t addr = get_vm_offset(p);
|
||||
kmem_unback(kernel_object, addr, size);
|
||||
}
|
||||
|
||||
/// Notify platform that we will not be using these pages
|
||||
template<ZeroMem zero_mem>
|
||||
void notify_using(void* p, size_t size)
|
||||
{
|
||||
vm_offset_t addr = get_vm_offset(p);
|
||||
int flags = M_WAITOK | ((zero_mem == YesZero) ? M_ZERO : 0);
|
||||
if (kmem_back(kernel_object, addr, size, flags) != KERN_SUCCESS)
|
||||
{
|
||||
error("Out of memory");
|
||||
}
|
||||
}
|
||||
|
||||
/// OS specific function for zeroing memory
|
||||
template<bool page_aligned = false>
|
||||
void zero(void* p, size_t size)
|
||||
{
|
||||
::bzero(p, size);
|
||||
}
|
||||
|
||||
template<bool committed>
|
||||
void* reserve(size_t* size, size_t align)
|
||||
{
|
||||
size_t request = *size;
|
||||
vm_offset_t addr;
|
||||
if (vmem_xalloc(
|
||||
kernel_arena,
|
||||
request,
|
||||
align,
|
||||
0,
|
||||
0,
|
||||
VMEM_ADDR_MIN,
|
||||
VMEM_ADDR_MAX,
|
||||
M_BESTFIT,
|
||||
&addr))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (committed)
|
||||
{
|
||||
if (
|
||||
kmem_back(kernel_object, addr, request, M_ZERO | M_WAITOK) !=
|
||||
KERN_SUCCESS)
|
||||
{
|
||||
vmem_xfree(kernel_arena, addr, request);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return get_vm_offset(addr);
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
88
src/pal/pal_freebsd.h
Normal file
88
src/pal/pal_freebsd.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(__FreeBSD__) && !defined(_KERNEL)
|
||||
# include "../ds/bits.h"
|
||||
# include "../mem/allocconfig.h"
|
||||
|
||||
# include <sys/mman.h>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class PALFBSD
|
||||
{
|
||||
public:
|
||||
static void error(const char* const str)
|
||||
{
|
||||
puts(str);
|
||||
abort();
|
||||
}
|
||||
|
||||
/// Notify platform that we will not be using these pages
|
||||
void notify_not_using(void* p, size_t size) noexcept
|
||||
{
|
||||
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||
madvise(p, size, MADV_FREE);
|
||||
}
|
||||
|
||||
/// Notify platform that we will not be using these pages
|
||||
template<ZeroMem zero_mem>
|
||||
void notify_using(void* p, size_t size) noexcept
|
||||
{
|
||||
assert(
|
||||
bits::is_aligned_block<OS_PAGE_SIZE>(p, size) || (zero_mem == NoZero));
|
||||
if (zero_mem == YesZero)
|
||||
zero(p, size);
|
||||
}
|
||||
|
||||
/// OS specific function for zeroing memory
|
||||
template<bool page_aligned = false>
|
||||
void zero(void* p, size_t size) noexcept
|
||||
{
|
||||
if (page_aligned || bits::is_aligned_block<OS_PAGE_SIZE>(p, size))
|
||||
{
|
||||
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||
void* r = mmap(
|
||||
p,
|
||||
size,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
|
||||
-1,
|
||||
0);
|
||||
|
||||
if (r != MAP_FAILED)
|
||||
return;
|
||||
}
|
||||
|
||||
bzero(p, size);
|
||||
}
|
||||
|
||||
template<bool committed>
|
||||
void* reserve(size_t* size, size_t align) noexcept
|
||||
{
|
||||
size_t request = *size;
|
||||
// Alignment must be a power of 2.
|
||||
assert(align == bits::next_pow2(align));
|
||||
|
||||
if (align == 0)
|
||||
{
|
||||
align = 1;
|
||||
}
|
||||
|
||||
size_t log2align = bits::next_pow2_bits(align);
|
||||
|
||||
void* p = mmap(
|
||||
NULL,
|
||||
request,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_ALIGNED(log2align),
|
||||
-1,
|
||||
0);
|
||||
|
||||
if (p == MAP_FAILED)
|
||||
error("Out of memory");
|
||||
|
||||
return p;
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
93
src/pal/pal_linux.h
Normal file
93
src/pal/pal_linux.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(__linux__)
|
||||
# include "../ds/bits.h"
|
||||
# include "../mem/allocconfig.h"
|
||||
|
||||
# include <stdio.h>
|
||||
# include <string.h>
|
||||
# include <sys/mman.h>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class PALLinux
|
||||
{
|
||||
public:
|
||||
static void error(const char* const str)
|
||||
{
|
||||
puts(str);
|
||||
abort();
|
||||
}
|
||||
|
||||
/// Notify platform that we will not be using these pages
|
||||
void notify_not_using(void* p, size_t size) noexcept
|
||||
{
|
||||
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||
// Do nothing. Don't call madvise here, as the system call slows the
|
||||
// allocator down too much.
|
||||
UNUSED(p);
|
||||
UNUSED(size);
|
||||
}
|
||||
|
||||
/// Notify platform that we will not be using these pages
|
||||
template<ZeroMem zero_mem>
|
||||
void notify_using(void* p, size_t size) noexcept
|
||||
{
|
||||
assert(
|
||||
bits::is_aligned_block<OS_PAGE_SIZE>(p, size) || (zero_mem == NoZero));
|
||||
|
||||
if (zero_mem == YesZero)
|
||||
zero<true>(p, size);
|
||||
}
|
||||
|
||||
/// OS specific function for zeroing memory
|
||||
template<bool page_aligned = false>
|
||||
void zero(void* p, size_t size) noexcept
|
||||
{
|
||||
if (page_aligned || bits::is_aligned_block<OS_PAGE_SIZE>(p, size))
|
||||
{
|
||||
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||
madvise(p, size, MADV_DONTNEED);
|
||||
}
|
||||
else
|
||||
{
|
||||
::memset(p, 0, size);
|
||||
}
|
||||
}
|
||||
|
||||
template<bool committed>
|
||||
void* reserve(size_t* size, size_t align) noexcept
|
||||
{
|
||||
size_t request = *size;
|
||||
// Add align, so we can guarantee to provide at least size.
|
||||
request += align;
|
||||
// Alignment must be a power of 2.
|
||||
assert(align == bits::next_pow2(align));
|
||||
|
||||
void* p = mmap(
|
||||
NULL,
|
||||
request,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
-1,
|
||||
0);
|
||||
|
||||
if (p == MAP_FAILED)
|
||||
error("Out of memory");
|
||||
*size = request;
|
||||
uintptr_t p0 = (uintptr_t)p;
|
||||
uintptr_t start = bits::align_up(p0, align);
|
||||
|
||||
if (start > (uintptr_t)p0)
|
||||
{
|
||||
uintptr_t end = bits::align_down(p0 + request, align);
|
||||
*size = end - start;
|
||||
munmap(p, start - p0);
|
||||
munmap((void*)end, (p0 + request) - end);
|
||||
p = (void*)start;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
58
src/pal/pal_open_enclave.h
Normal file
58
src/pal/pal_open_enclave.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "pal_plain.h"
|
||||
#ifdef OPEN_ENCLAVE
|
||||
extern "C" const void* __oe_get_heap_base();
|
||||
extern "C" const void* __oe_get_heap_end();
|
||||
extern "C" void* oe_memset(void* p, int c, size_t size);
|
||||
extern "C" void oe_abort();
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class PALOpenEnclave
|
||||
{
|
||||
std::atomic<uintptr_t> oe_base;
|
||||
|
||||
public:
|
||||
static void error(const char* const str)
|
||||
{
|
||||
UNUSED(str);
|
||||
oe_abort();
|
||||
}
|
||||
|
||||
template<bool committed>
|
||||
void* reserve(size_t* size, size_t align) noexcept
|
||||
{
|
||||
if (oe_base == 0)
|
||||
{
|
||||
uintptr_t dummy = 0;
|
||||
oe_base.compare_exchange_strong(dummy, (uintptr_t)__oe_get_heap_base());
|
||||
}
|
||||
|
||||
uintptr_t old_base = oe_base;
|
||||
uintptr_t old_base2 = old_base;
|
||||
uintptr_t next_base;
|
||||
auto end = (uintptr_t)__oe_get_heap_end();
|
||||
do
|
||||
{
|
||||
old_base2 = old_base;
|
||||
auto new_base = bits::align_up(old_base, align);
|
||||
next_base = new_base + *size;
|
||||
|
||||
if (next_base > end)
|
||||
error("Out of memory");
|
||||
|
||||
} while (oe_base.compare_exchange_strong(old_base, next_base));
|
||||
|
||||
*size = next_base - old_base2;
|
||||
return (void*)old_base;
|
||||
}
|
||||
|
||||
template<bool page_aligned = false>
|
||||
void zero(void* p, size_t size) noexcept
|
||||
{
|
||||
oe_memset(p, 0, size);
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
34
src/pal/pal_plain.h
Normal file
34
src/pal/pal_plain.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/bits.h"
|
||||
#include "../mem/allocconfig.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
// Can be extended
|
||||
// Will require a reserve method in subclasses.
|
||||
template<class State>
|
||||
class PALPlainMixin : public State
|
||||
{
|
||||
public:
|
||||
PALPlainMixin() : State() {}
|
||||
|
||||
// Notify platform that we will not be using these pages
|
||||
void notify_not_using(void*, size_t) noexcept {}
|
||||
|
||||
// Notify platform that we will not be using these pages
|
||||
template<ZeroMem zero_mem>
|
||||
void notify_using(void* p, size_t size) noexcept
|
||||
{
|
||||
if constexpr (zero_mem == YesZero)
|
||||
{
|
||||
State::zero(p, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
UNUSED(p);
|
||||
UNUSED(size);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
111
src/pal/pal_windows.h
Normal file
111
src/pal/pal_windows.h
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/bits.h"
|
||||
#include "../mem/allocconfig.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class PALWindows
|
||||
{
|
||||
public:
|
||||
static void error(const char* const str)
|
||||
{
|
||||
puts(str);
|
||||
abort();
|
||||
}
|
||||
|
||||
/// Notify platform that we will not be using these pages
|
||||
void notify_not_using(void* p, size_t size) noexcept
|
||||
{
|
||||
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||
|
||||
BOOL ok = VirtualFree(p, size, MEM_DECOMMIT);
|
||||
|
||||
if (!ok)
|
||||
error("VirtualFree failed");
|
||||
}
|
||||
|
||||
/// Notify platform that we will not be using these pages
|
||||
template<ZeroMem zero_mem>
|
||||
void notify_using(void* p, size_t size) noexcept
|
||||
{
|
||||
assert(
|
||||
bits::is_aligned_block<OS_PAGE_SIZE>(p, size) || (zero_mem == NoZero));
|
||||
|
||||
void* r = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE);
|
||||
|
||||
if (r == nullptr)
|
||||
error("out of memory");
|
||||
}
|
||||
|
||||
/// OS specific function for zeroing memory
|
||||
template<bool page_aligned = false>
|
||||
void zero(void* p, size_t size) noexcept
|
||||
{
|
||||
if (page_aligned || bits::is_aligned_block<OS_PAGE_SIZE>(p, size))
|
||||
{
|
||||
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||
notify_not_using(p, size);
|
||||
notify_using<YesZero>(p, size);
|
||||
}
|
||||
else
|
||||
::memset(p, 0, size);
|
||||
}
|
||||
|
||||
# ifdef USE_SYSTEMATIC_TESTING
|
||||
size_t& systematic_bump_ptr()
|
||||
{
|
||||
static size_t bump_ptr = (size_t)0x4000'0000'0000;
|
||||
return bump_ptr;
|
||||
}
|
||||
# endif
|
||||
|
||||
template<bool committed>
|
||||
void* reserve(size_t* size, size_t align) noexcept
|
||||
{
|
||||
// Add align, so we can guarantee to provide at least size.
|
||||
size_t request = *size + align;
|
||||
|
||||
// Alignment must be a power of 2.
|
||||
assert(align == bits::next_pow2(align));
|
||||
|
||||
DWORD flags = MEM_RESERVE;
|
||||
|
||||
if (committed)
|
||||
flags |= MEM_COMMIT;
|
||||
|
||||
void* p;
|
||||
# ifdef USE_SYSTEMATIC_TESTING
|
||||
size_t retries = 1000;
|
||||
do
|
||||
{
|
||||
p = VirtualAlloc(
|
||||
(void*)systematic_bump_ptr(), request, flags, PAGE_READWRITE);
|
||||
|
||||
systematic_bump_ptr() += request;
|
||||
retries--;
|
||||
} while (p == nullptr && retries > 0);
|
||||
# else
|
||||
p = VirtualAlloc(nullptr, request, flags, PAGE_READWRITE);
|
||||
# endif
|
||||
|
||||
uintptr_t aligned_p = bits::align_up((size_t)p, align);
|
||||
|
||||
if (aligned_p != (uintptr_t)p)
|
||||
{
|
||||
auto extra_bit = aligned_p - (uintptr_t)p;
|
||||
uintptr_t end = (uintptr_t)p + request;
|
||||
// Attempt to align end of the block.
|
||||
VirtualAlloc((void*)end, extra_bit, flags, PAGE_READWRITE);
|
||||
}
|
||||
*size = request;
|
||||
return p;
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
3
src/snmalloc.h
Normal file
3
src/snmalloc.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#include "mem/threadalloc.h"
|
||||
52
src/test/func/fixed_region/fixed_region.cc
Normal file
52
src/test/func/fixed_region/fixed_region.cc
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
#define OPEN_ENCLAVE
|
||||
#define OPEN_ENCLAVE_SIMULATION
|
||||
#define USE_RESERVE_MULTIPLE 1
|
||||
#include <iostream>
|
||||
#include <snmalloc.h>
|
||||
|
||||
void* oe_base;
|
||||
void* oe_end;
|
||||
extern "C" const void* __oe_get_heap_base()
|
||||
{
|
||||
return oe_base;
|
||||
}
|
||||
|
||||
extern "C" const void* __oe_get_heap_end()
|
||||
{
|
||||
return oe_end;
|
||||
}
|
||||
|
||||
extern "C" void* oe_memset(void* p, int c, size_t size)
|
||||
{
|
||||
return memset(p, c, size);
|
||||
}
|
||||
|
||||
extern "C" void oe_abort()
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
using namespace snmalloc;
|
||||
int main()
|
||||
{
|
||||
DefaultPal pal;
|
||||
|
||||
size_t size = 1ULL << 28;
|
||||
oe_base = pal.reserve<true>(&size, 0);
|
||||
oe_end = (uint8_t*)oe_base + size;
|
||||
std::cout << "Allocated region " << oe_base << " - " << oe_end << std::endl;
|
||||
|
||||
auto a = ThreadAlloc::get();
|
||||
|
||||
for (size_t i = 0; i < 1000; i++)
|
||||
{
|
||||
auto r1 = a->alloc(100);
|
||||
std::cout << "Allocated object " << r1 << std::endl;
|
||||
|
||||
if (oe_base > r1)
|
||||
abort();
|
||||
if (oe_end < r1)
|
||||
abort();
|
||||
}
|
||||
}
|
||||
275
src/test/func/memory/memory.cc
Normal file
275
src/test/func/memory/memory.cc
Normal file
@@ -0,0 +1,275 @@
|
||||
#include <snmalloc.h>
|
||||
#include <test/opt.h>
|
||||
#include <test/xoroshiro.h>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
void test_alloc_dealloc_64k()
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
constexpr size_t count = 1 << 12;
|
||||
constexpr size_t outer_count = 12;
|
||||
void* garbage[count];
|
||||
void* keep_alive[outer_count];
|
||||
|
||||
for (size_t j = 0; j < outer_count; j++)
|
||||
{
|
||||
// Allocate 64k of 16byte allocs
|
||||
// This will fill the short slab, and then start a new slab.
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
garbage[i] = alloc->alloc(16);
|
||||
}
|
||||
|
||||
// Allocate one object on the second slab
|
||||
keep_alive[j] = alloc->alloc(16);
|
||||
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
alloc->dealloc(garbage[i]);
|
||||
}
|
||||
}
|
||||
for (size_t j = 0; j < outer_count; j++)
|
||||
{
|
||||
alloc->dealloc(keep_alive[j]);
|
||||
}
|
||||
}
|
||||
|
||||
void test_random_allocation()
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
std::unordered_set<void*> allocated;
|
||||
|
||||
constexpr size_t count = 10000;
|
||||
constexpr size_t outer_count = 10;
|
||||
void* objects[count];
|
||||
for (size_t i = 0; i < count; i++)
|
||||
objects[i] = nullptr;
|
||||
|
||||
// Randomly allocate and deallocate objects
|
||||
xoroshiro::p128r32 r;
|
||||
size_t alloc_count = 0;
|
||||
for (size_t j = 0; j < outer_count; j++)
|
||||
{
|
||||
auto just_dealloc = r.next() % 2 == 1;
|
||||
auto duration = r.next() % count;
|
||||
for (size_t i = 0; i < duration; i++)
|
||||
{
|
||||
auto index = r.next();
|
||||
auto& cell = objects[index % count];
|
||||
if (cell != nullptr)
|
||||
{
|
||||
alloc->dealloc(cell);
|
||||
allocated.erase(cell);
|
||||
cell = nullptr;
|
||||
alloc_count--;
|
||||
}
|
||||
if (!just_dealloc)
|
||||
{
|
||||
cell = alloc->alloc(16);
|
||||
auto pair = allocated.insert(cell);
|
||||
// Check not already allocated
|
||||
assert(pair.second);
|
||||
UNUSED(pair);
|
||||
alloc_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (alloc_count == 0 && just_dealloc)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deallocate all the remaining objects
|
||||
for (size_t i = 0; i < count; i++)
|
||||
if (objects[i] != nullptr)
|
||||
alloc->dealloc(objects[i]);
|
||||
}
|
||||
|
||||
void test_calloc()
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
for (size_t size = 16; size <= (1 << 24); size <<= 1)
|
||||
{
|
||||
void* p = alloc->alloc(size);
|
||||
memset(p, 0xFF, size);
|
||||
alloc->dealloc(p, size);
|
||||
|
||||
p = alloc->alloc<YesZero>(size);
|
||||
|
||||
for (size_t i = 0; i < size; i++)
|
||||
{
|
||||
if (((char*)p)[i] != 0)
|
||||
abort();
|
||||
}
|
||||
|
||||
alloc->dealloc(p, size);
|
||||
}
|
||||
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
}
|
||||
|
||||
void test_double_alloc()
|
||||
{
|
||||
auto* a1 = current_alloc_pool()->acquire();
|
||||
auto* a2 = current_alloc_pool()->acquire();
|
||||
|
||||
const size_t n = (1 << 16) / 32;
|
||||
|
||||
for (size_t k = 0; k < 4; k++)
|
||||
{
|
||||
std::unordered_set<void*> set1;
|
||||
std::unordered_set<void*> set2;
|
||||
|
||||
for (size_t i = 0; i < (n * 2); i++)
|
||||
{
|
||||
void* p = a1->alloc(20);
|
||||
assert(set1.find(p) == set1.end());
|
||||
set1.insert(p);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (n * 2); i++)
|
||||
{
|
||||
void* p = a2->alloc(20);
|
||||
assert(set2.find(p) == set2.end());
|
||||
set2.insert(p);
|
||||
}
|
||||
|
||||
while (!set1.empty())
|
||||
{
|
||||
auto it = set1.begin();
|
||||
a2->dealloc(*it, 20);
|
||||
set1.erase(it);
|
||||
}
|
||||
|
||||
while (!set2.empty())
|
||||
{
|
||||
auto it = set2.begin();
|
||||
a1->dealloc(*it, 20);
|
||||
set2.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
current_alloc_pool()->release(a1);
|
||||
current_alloc_pool()->release(a2);
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
}
|
||||
|
||||
void test_external_pointer()
|
||||
{
|
||||
// Malloc does not have an external pointer querying mechanism.
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
for (uint8_t sc = 0; sc < NUM_SIZECLASSES; sc++)
|
||||
{
|
||||
size_t size = sizeclass_to_size(sc);
|
||||
void* p1 = alloc->alloc(size);
|
||||
|
||||
for (size_t offset = 0; offset < size; offset += 17)
|
||||
{
|
||||
void* p2 = (void*)((size_t)p1 + offset);
|
||||
void* p3 = Alloc::external_pointer(p2);
|
||||
void* p4 = Alloc::external_pointer<End>(p2);
|
||||
UNUSED(p3);
|
||||
UNUSED(p4);
|
||||
assert(p1 == p3);
|
||||
assert((size_t)p4 == (size_t)p1 + size - 1);
|
||||
}
|
||||
|
||||
alloc->dealloc(p1, size);
|
||||
}
|
||||
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
};
|
||||
|
||||
void check_offset(void* base, void* interior)
|
||||
{
|
||||
void* calced_base = Alloc::external_pointer((void*)interior);
|
||||
if (calced_base != (void*)base)
|
||||
abort();
|
||||
}
|
||||
|
||||
void check_external_pointer_large(size_t* base)
|
||||
{
|
||||
size_t size = *base;
|
||||
char* curr = (char*)base;
|
||||
for (size_t offset = 0; offset < size; offset += 1 << 24)
|
||||
{
|
||||
check_offset(base, (void*)(curr + offset));
|
||||
check_offset(base, (void*)(curr + offset + (1 << 24) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
void test_external_pointer_large()
|
||||
{
|
||||
xoroshiro::p128r64 r;
|
||||
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
constexpr size_t count_log = 5;
|
||||
constexpr size_t count = 1 << count_log;
|
||||
// Pre allocate all the objects
|
||||
size_t* objects[count];
|
||||
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
size_t rand = r.next() & ((1 << 28) - 1);
|
||||
size_t size = (1 << 24) + rand;
|
||||
// store object
|
||||
objects[i] = (size_t*)alloc->alloc(size);
|
||||
// Store allocators size for this object
|
||||
*objects[i] = Alloc::alloc_size(objects[i]);
|
||||
|
||||
check_external_pointer_large(objects[i]);
|
||||
if (i > 0)
|
||||
check_external_pointer_large(objects[i - 1]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
check_external_pointer_large(objects[i]);
|
||||
}
|
||||
|
||||
// Deallocate everything
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
alloc->dealloc(objects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void test_alloc_16M()
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
// sizes >= 16M use large_alloc
|
||||
const size_t size = 16'000'000;
|
||||
|
||||
void* p1 = alloc->alloc(size);
|
||||
assert(Alloc::alloc_size(Alloc::external_pointer(p1)) >= size);
|
||||
alloc->dealloc(p1);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
#ifdef USE_SYSTEMATIC_TESTING
|
||||
opt::Opt opt(argc, argv);
|
||||
size_t seed = opt.is<size_t>("--seed", 0);
|
||||
Virtual::systematic_bump_ptr() += seed << 17;
|
||||
#else
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
#endif
|
||||
|
||||
test_external_pointer_large();
|
||||
test_alloc_dealloc_64k();
|
||||
test_random_allocation();
|
||||
test_calloc();
|
||||
test_double_alloc();
|
||||
test_external_pointer();
|
||||
test_alloc_16M();
|
||||
|
||||
return 0;
|
||||
}
|
||||
36
src/test/func/rounding/rounding.cc
Normal file
36
src/test/func/rounding/rounding.cc
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <snmalloc.h>
|
||||
#include <test/opt.h>
|
||||
#include <test/xoroshiro.h>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
// Check for all sizeclass that we correctly round every offset within
|
||||
// a superslab to the correct value, by comparing with the standard
|
||||
// unoptimised version using division.
|
||||
// Also check we correctly determine multiples using optimized check.
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
|
||||
for (size_t size_class = 0; size_class < NUM_SIZECLASSES; size_class++)
|
||||
{
|
||||
size_t rsize = sizeclass_to_size((uint8_t)size_class);
|
||||
for (size_t offset = 0; offset < SUPERSLAB_SIZE; offset++)
|
||||
{
|
||||
size_t rounded = (offset / rsize) * rsize;
|
||||
bool mod_0 = (offset % rsize) == 0;
|
||||
|
||||
size_t opt_rounded = round_by_sizeclass(rsize, offset);
|
||||
if (rounded != opt_rounded)
|
||||
abort();
|
||||
|
||||
bool opt_mod_0 = is_multiple_of_sizeclass(rsize, offset);
|
||||
if (opt_mod_0 != mod_0)
|
||||
abort();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
249
src/test/histogram.h
Normal file
249
src/test/histogram.h
Normal file
@@ -0,0 +1,249 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_MEASURE
|
||||
# include "../ds/flaglock.h"
|
||||
|
||||
# include <algorithm>
|
||||
# include <iomanip>
|
||||
# include <iostream>
|
||||
# define MEASURE_TIME_MARKERS(id, minbits, maxbits, markers) \
|
||||
static constexpr const char* const id##_time_markers[] = markers; \
|
||||
static histogram::Global<histogram::Histogram<uint64_t, minbits, maxbits>> \
|
||||
id##_time_global(#id, __FILE__, __LINE__, id##_time_markers); \
|
||||
static thread_local histogram::Histogram<uint64_t, minbits, maxbits> \
|
||||
id##_time_local(id##_time_global); \
|
||||
histogram::MeasureTime<histogram::Histogram<uint64_t, minbits, maxbits>> \
|
||||
id##_time(id##_time_local);
|
||||
|
||||
# define MEASURE_TIME(id, minbits, maxbits) \
|
||||
MEASURE_TIME_MARKERS(id, minbits, maxbits, {nullptr})
|
||||
|
||||
# define MARKERS(...) \
|
||||
{ \
|
||||
__VA_ARGS__, nullptr \
|
||||
}
|
||||
|
||||
namespace histogram
|
||||
{
|
||||
using namespace snmalloc;
|
||||
|
||||
template<class H>
|
||||
class Global;
|
||||
|
||||
template<
|
||||
class V,
|
||||
size_t LOW_BITS,
|
||||
size_t HIGH_BITS,
|
||||
size_t INTERMEDIATE_BITS = LOW_BITS>
|
||||
class Histogram
|
||||
{
|
||||
public:
|
||||
using This = Histogram<V, LOW_BITS, HIGH_BITS, INTERMEDIATE_BITS>;
|
||||
friend Global<This>;
|
||||
|
||||
static_assert(LOW_BITS < HIGH_BITS, "LOW_BITS must be less than HIGH_BITS");
|
||||
|
||||
static constexpr V LOW = (V)((size_t)1 << LOW_BITS);
|
||||
static constexpr V HIGH = (V)((size_t)1 << HIGH_BITS);
|
||||
static constexpr size_t BUCKETS =
|
||||
((HIGH_BITS - LOW_BITS) << INTERMEDIATE_BITS) + 2;
|
||||
|
||||
private:
|
||||
V high = (std::numeric_limits<V>::min)();
|
||||
size_t overflow;
|
||||
size_t count[BUCKETS];
|
||||
|
||||
Global<This>* global;
|
||||
|
||||
public:
|
||||
Histogram() : global(nullptr) {}
|
||||
Histogram(Global<This>& g) : global(&g) {}
|
||||
|
||||
~Histogram()
|
||||
{
|
||||
if (global != nullptr)
|
||||
global->add(*this);
|
||||
}
|
||||
|
||||
void record(V value)
|
||||
{
|
||||
if (value > high)
|
||||
high = value;
|
||||
|
||||
if (value >= HIGH)
|
||||
{
|
||||
overflow++;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto i = get_index(value);
|
||||
assert(i < BUCKETS);
|
||||
count[i]++;
|
||||
}
|
||||
}
|
||||
|
||||
V get_high()
|
||||
{
|
||||
return high;
|
||||
}
|
||||
|
||||
size_t get_overflow()
|
||||
{
|
||||
return overflow;
|
||||
}
|
||||
|
||||
size_t get_buckets()
|
||||
{
|
||||
return BUCKETS;
|
||||
}
|
||||
|
||||
size_t get_count(size_t index)
|
||||
{
|
||||
if (index >= BUCKETS)
|
||||
return 0;
|
||||
|
||||
return count[index];
|
||||
}
|
||||
|
||||
static std::pair<V, V> get_range(size_t index)
|
||||
{
|
||||
if (index >= BUCKETS)
|
||||
return std::make_pair(HIGH, HIGH);
|
||||
|
||||
if (index == 0)
|
||||
return std::make_pair(0, get_value(index));
|
||||
|
||||
return std::make_pair(get_value(index - 1) + 1, get_value(index));
|
||||
}
|
||||
|
||||
void add(This& that)
|
||||
{
|
||||
high = (std::max)(high, that.high);
|
||||
overflow += that.overflow;
|
||||
|
||||
for (size_t i = 0; i < BUCKETS; i++)
|
||||
count[i] += that.count[i];
|
||||
}
|
||||
|
||||
void print(std::ostream& o)
|
||||
{
|
||||
o << "\tHigh: " << high << std::endl
|
||||
<< "\tOverflow: " << overflow << std::endl;
|
||||
|
||||
size_t grand_total = overflow;
|
||||
for (size_t i = 0; i < BUCKETS; i++)
|
||||
grand_total += count[i];
|
||||
|
||||
size_t old_percentage = 0;
|
||||
size_t cumulative_total = 0;
|
||||
for (size_t i = 0; i < BUCKETS; i++)
|
||||
{
|
||||
auto r = get_range(i);
|
||||
|
||||
cumulative_total += count[i];
|
||||
|
||||
o << "\t" << std::setfill(' ') << std::setw(6) << std::get<0>(r) << ".."
|
||||
<< std::setfill(' ') << std::setw(6) << std::get<1>(r) << ": "
|
||||
<< std::setfill(' ') << std::setw(10) << count[i];
|
||||
|
||||
auto percentage = (cumulative_total * 100 / grand_total);
|
||||
if (percentage != old_percentage)
|
||||
{
|
||||
old_percentage = percentage;
|
||||
o << std::setfill(' ') << std::setw(20)
|
||||
<< (cumulative_total * 100 / grand_total) << "%";
|
||||
}
|
||||
|
||||
o << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t get_index(V value)
|
||||
{
|
||||
return bits::to_exp_mant<INTERMEDIATE_BITS, LOW_BITS - INTERMEDIATE_BITS>(
|
||||
value);
|
||||
}
|
||||
|
||||
static V get_value(size_t index)
|
||||
{
|
||||
return bits::
|
||||
from_exp_mant<INTERMEDIATE_BITS, LOW_BITS - INTERMEDIATE_BITS>(index);
|
||||
}
|
||||
};
|
||||
|
||||
template<class H>
|
||||
class Global
|
||||
{
|
||||
private:
|
||||
const char* name;
|
||||
const char* file;
|
||||
size_t line;
|
||||
const char* const* markers;
|
||||
|
||||
std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||
H aggregate;
|
||||
|
||||
public:
|
||||
Global(
|
||||
const char* name_,
|
||||
const char* file_,
|
||||
size_t line_,
|
||||
const char* const* markers)
|
||||
: name(name_), file(file_), line(line_), markers(markers)
|
||||
{}
|
||||
|
||||
~Global()
|
||||
{
|
||||
print();
|
||||
}
|
||||
|
||||
void add(H& histogram)
|
||||
{
|
||||
FlagLock f(lock);
|
||||
aggregate.add(histogram);
|
||||
}
|
||||
|
||||
private:
|
||||
void print()
|
||||
{
|
||||
std::cout << name;
|
||||
|
||||
if (markers != nullptr)
|
||||
{
|
||||
std::cout << ": ";
|
||||
size_t i = 0;
|
||||
|
||||
while (markers[i] != nullptr)
|
||||
std::cout << markers[i++] << " ";
|
||||
}
|
||||
|
||||
std::cout << std::endl << file << ":" << line << std::endl;
|
||||
|
||||
aggregate.print(std::cout);
|
||||
}
|
||||
};
|
||||
|
||||
template<class H>
|
||||
class MeasureTime
|
||||
{
|
||||
private:
|
||||
H& histogram;
|
||||
uint64_t t;
|
||||
|
||||
public:
|
||||
MeasureTime(H& histogram_) : histogram(histogram_)
|
||||
{
|
||||
t = bits::benchmark_time_start();
|
||||
}
|
||||
|
||||
~MeasureTime()
|
||||
{
|
||||
histogram.record(bits::benchmark_time_end() - t);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#else
|
||||
# define MEASURE_TIME(id, minbits, maxbits)
|
||||
# define MEASURE_TIME_MARKERS(id, minbits, maxbits, markers)
|
||||
#endif
|
||||
14
src/test/measuretime.h
Normal file
14
src/test/measuretime.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#define DO_TIME(name, code) \
|
||||
{ \
|
||||
auto start__ = std::chrono::high_resolution_clock::now(); \
|
||||
code auto finish__ = std::chrono::high_resolution_clock::now(); \
|
||||
auto diff__ = finish__ - start__; \
|
||||
std::cout << name << ": " << std::setw(12) << diff__.count() << " ns" \
|
||||
<< std::endl; \
|
||||
}
|
||||
91
src/test/opt.h
Normal file
91
src/test/opt.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
namespace opt
|
||||
{
|
||||
class Opt
|
||||
{
|
||||
private:
|
||||
int argc;
|
||||
char** argv;
|
||||
|
||||
public:
|
||||
Opt(int argc, char** argv) : argc(argc), argv(argv) {}
|
||||
|
||||
bool has(const char* opt)
|
||||
{
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (!strcmp(opt, argv[i]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T is(const char* opt, T def)
|
||||
{
|
||||
size_t len = strlen(opt);
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
const char* p = param(opt, len, i);
|
||||
|
||||
if (p != nullptr)
|
||||
{
|
||||
char* end = nullptr;
|
||||
T r;
|
||||
|
||||
if (std::is_unsigned<T>::value)
|
||||
r = (T)strtoull(p, &end, 10);
|
||||
else
|
||||
r = (T)strtoll(p, &end, 10);
|
||||
|
||||
if ((r == 0) && (end == p))
|
||||
return def;
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
const char* is(const char* opt, const char* def)
|
||||
{
|
||||
size_t len = strlen(opt);
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
const char* p = param(opt, len, i);
|
||||
|
||||
if (p != nullptr)
|
||||
return p;
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* param(const char* opt, size_t len, int i)
|
||||
{
|
||||
if (strncmp(opt, argv[i], len))
|
||||
return nullptr;
|
||||
|
||||
switch (argv[i][len])
|
||||
{
|
||||
case '\0':
|
||||
return (i < (argc - 1)) ? argv[i + 1] : nullptr;
|
||||
case '=':
|
||||
return &argv[i][len + 1];
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
176
src/test/perf/contention/contention.cc
Normal file
176
src/test/perf/contention/contention.cc
Normal file
@@ -0,0 +1,176 @@
|
||||
#include "test/measuretime.h"
|
||||
#include "test/opt.h"
|
||||
#include "test/usage.h"
|
||||
#include "test/xoroshiro.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <snmalloc.h>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
bool use_malloc = false;
|
||||
|
||||
template<void f(size_t id)>
|
||||
class ParallelTest
|
||||
{
|
||||
private:
|
||||
std::atomic<bool> flag = false;
|
||||
std::atomic<size_t> ready = 0;
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
std::atomic<size_t> complete = 0;
|
||||
|
||||
size_t cores;
|
||||
|
||||
void run(size_t id)
|
||||
{
|
||||
auto prev = ready.fetch_add(1);
|
||||
if (prev + 1 == cores)
|
||||
{
|
||||
start = bits::tick();
|
||||
flag = true;
|
||||
}
|
||||
while (!flag)
|
||||
bits::pause();
|
||||
|
||||
f(id);
|
||||
|
||||
prev = complete.fetch_add(1);
|
||||
if (prev + 1 == cores)
|
||||
{
|
||||
end = bits::tick();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
ParallelTest(size_t cores) : cores(cores)
|
||||
{
|
||||
std::thread* t = new std::thread[cores];
|
||||
|
||||
for (size_t i = 0; i < cores; i++)
|
||||
{
|
||||
t[i] = std::thread(&ParallelTest::run, this, i);
|
||||
}
|
||||
// Wait for all the threads.
|
||||
for (size_t i = 0; i < cores; i++)
|
||||
{
|
||||
t[i].join();
|
||||
}
|
||||
|
||||
delete[] t;
|
||||
}
|
||||
|
||||
uint64_t time()
|
||||
{
|
||||
return end - start;
|
||||
}
|
||||
};
|
||||
|
||||
std::atomic<size_t*>* contention;
|
||||
size_t swapsize;
|
||||
size_t swapcount;
|
||||
|
||||
void test_tasks_f(size_t id)
|
||||
{
|
||||
Alloc* a = ThreadAlloc::get();
|
||||
xoroshiro::p128r32 r(id + 5000);
|
||||
|
||||
for (size_t n = 0; n < swapcount; n++)
|
||||
{
|
||||
size_t size = 16 + (r.next() % 1024);
|
||||
size_t* res = (size_t*)(use_malloc ? malloc(size) : a->alloc(size));
|
||||
|
||||
*res = size;
|
||||
size_t* out =
|
||||
contention[n % swapsize].exchange(res, std::memory_order_relaxed);
|
||||
|
||||
if (out != nullptr)
|
||||
{
|
||||
size = *out;
|
||||
if (use_malloc)
|
||||
free(out);
|
||||
else
|
||||
a->dealloc(out, size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void test_tasks(size_t num_tasks, size_t count, size_t size)
|
||||
{
|
||||
Alloc* a = ThreadAlloc::get();
|
||||
|
||||
contention = new std::atomic<size_t*>[size];
|
||||
xoroshiro::p128r32 r;
|
||||
|
||||
for (size_t n = 0; n < size; n++)
|
||||
{
|
||||
size_t alloc_size = 16 + (r.next() % 1024);
|
||||
size_t* res =
|
||||
(size_t*)(use_malloc ? malloc(alloc_size) : a->alloc(alloc_size));
|
||||
*res = alloc_size;
|
||||
contention[n] = res;
|
||||
}
|
||||
swapcount = count;
|
||||
swapsize = size;
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
Stats s0;
|
||||
current_alloc_pool()->aggregate_stats(s0);
|
||||
#endif
|
||||
|
||||
{
|
||||
ParallelTest<test_tasks_f> test(num_tasks);
|
||||
|
||||
std::cout << "Task test, " << num_tasks << " threads, " << count
|
||||
<< " swaps per thread " << test.time() << "ticks" << std::endl;
|
||||
|
||||
for (size_t n = 0; n < swapsize; n++)
|
||||
{
|
||||
if (contention[n] != nullptr)
|
||||
{
|
||||
if (use_malloc)
|
||||
free(contention[n]);
|
||||
else
|
||||
a->dealloc(contention[n], *contention[n]);
|
||||
}
|
||||
}
|
||||
|
||||
delete[] contention;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
#endif
|
||||
};
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
opt::Opt opt(argc, argv);
|
||||
size_t cores = opt.is<size_t>("--cores", 8);
|
||||
|
||||
size_t count = opt.is<size_t>("--swapcount", 1 << 20);
|
||||
size_t size = opt.is<size_t>("--swapsize", 1 << 18);
|
||||
use_malloc = opt.has("--use_malloc");
|
||||
|
||||
std::cout << "Allocator is " << (use_malloc ? "System" : "snmalloc")
|
||||
<< std::endl;
|
||||
|
||||
for (size_t i = cores; i > 0; i >>= 1)
|
||||
test_tasks(i, count, size);
|
||||
|
||||
if (opt.has("--stats"))
|
||||
{
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
Stats s;
|
||||
current_alloc_pool()->aggregate_stats(s);
|
||||
s.print<Alloc>(std::cout);
|
||||
#endif
|
||||
|
||||
usage::print_memory();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
78
src/test/perf/external_pointer/externalpointer.cc
Normal file
78
src/test/perf/external_pointer/externalpointer.cc
Normal file
@@ -0,0 +1,78 @@
|
||||
#include <snmalloc.h>
|
||||
#include <test/measuretime.h>
|
||||
#include <test/xoroshiro.h>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
constexpr size_t count_log = 20;
|
||||
constexpr size_t count = 1 << count_log;
|
||||
// Pre allocate all the objects
|
||||
size_t* objects[count];
|
||||
|
||||
NOINLINE void setup(xoroshiro::p128r64& r, Alloc* alloc)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
size_t rand = (size_t)r.next();
|
||||
size_t offset = bits::clz(rand);
|
||||
if (offset > 30)
|
||||
offset = 30;
|
||||
size_t size = (rand & 15) << offset;
|
||||
if (size < 16)
|
||||
size = 16;
|
||||
// store object
|
||||
objects[i] = (size_t*)alloc->alloc(size);
|
||||
// Store allocators size for this object
|
||||
*objects[i] = Alloc::alloc_size(objects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
NOINLINE void teardown(Alloc* alloc)
|
||||
{
|
||||
// Deallocate everything
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
alloc->dealloc(objects[i]);
|
||||
}
|
||||
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
}
|
||||
|
||||
void test_external_pointer(xoroshiro::p128r64& r)
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
setup(r, alloc);
|
||||
|
||||
DO_TIME("External pointer queries ", {
|
||||
for (size_t i = 0; i < 10000000; i++)
|
||||
{
|
||||
size_t rand = (size_t)r.next();
|
||||
size_t oid = rand & (((size_t)1 << count_log) - 1);
|
||||
size_t* external_ptr = objects[oid];
|
||||
size_t size = *external_ptr;
|
||||
size_t offset = (size >> 4) * (rand & 15);
|
||||
size_t interior_ptr = ((size_t)external_ptr) + offset;
|
||||
void* calced_external = Alloc::external_pointer((void*)interior_ptr);
|
||||
if (calced_external != external_ptr)
|
||||
abort();
|
||||
}
|
||||
});
|
||||
|
||||
teardown(alloc);
|
||||
}
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
xoroshiro::p128r64 r;
|
||||
#if NDEBUG
|
||||
size_t nn = 30;
|
||||
#else
|
||||
size_t nn = 3;
|
||||
#endif
|
||||
|
||||
for (size_t n = 0; n < nn; n++)
|
||||
test_external_pointer(r);
|
||||
return 0;
|
||||
}
|
||||
83
src/test/perf/singlethread/singlethread.cc
Normal file
83
src/test/perf/singlethread/singlethread.cc
Normal file
@@ -0,0 +1,83 @@
|
||||
#include <snmalloc.h>
|
||||
#include <test/measuretime.h>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
template<ZeroMem zero_mem>
|
||||
void test_alloc_dealloc(size_t count, size_t size, bool write)
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
DO_TIME(
|
||||
"Count: " << std::setw(6) << count << ", Size: " << std::setw(6) << size
|
||||
<< ", ZeroMem: " << (zero_mem == YesZero) << ", Write: " << write,
|
||||
{
|
||||
std::unordered_set<void*> set;
|
||||
|
||||
// alloc 1.5x objects
|
||||
for (size_t i = 0; i < ((count * 3) / 2); i++)
|
||||
{
|
||||
void* p = alloc->alloc<zero_mem>(size);
|
||||
assert(set.find(p) == set.end());
|
||||
|
||||
if (write)
|
||||
*(int*)p = 4;
|
||||
|
||||
set.insert(p);
|
||||
}
|
||||
|
||||
// free 0.25x of the objects
|
||||
for (size_t i = 0; i < (count / 4); i++)
|
||||
{
|
||||
auto it = set.begin();
|
||||
void* p = *it;
|
||||
alloc->dealloc(p, size);
|
||||
set.erase(it);
|
||||
assert(set.find(p) == set.end());
|
||||
}
|
||||
|
||||
// alloc 1x objects
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
void* p = alloc->alloc<zero_mem>(size);
|
||||
assert(set.find(p) == set.end());
|
||||
|
||||
if (write)
|
||||
*(int*)p = 4;
|
||||
|
||||
set.insert(p);
|
||||
}
|
||||
|
||||
// free everything
|
||||
while (!set.empty())
|
||||
{
|
||||
auto it = set.begin();
|
||||
alloc->dealloc(*it, size);
|
||||
set.erase(it);
|
||||
}
|
||||
});
|
||||
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
}
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
for (size_t size = 16; size <= 128; size <<= 1)
|
||||
{
|
||||
test_alloc_dealloc<NoZero>(1 << 15, size, false);
|
||||
test_alloc_dealloc<NoZero>(1 << 15, size, true);
|
||||
test_alloc_dealloc<YesZero>(1 << 15, size, false);
|
||||
test_alloc_dealloc<YesZero>(1 << 15, size, true);
|
||||
}
|
||||
|
||||
for (size_t size = 1 << 12; size <= 1 << 17; size <<= 1)
|
||||
{
|
||||
test_alloc_dealloc<NoZero>(1 << 10, size, false);
|
||||
test_alloc_dealloc<NoZero>(1 << 10, size, true);
|
||||
test_alloc_dealloc<YesZero>(1 << 10, size, false);
|
||||
test_alloc_dealloc<YesZero>(1 << 10, size, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
42
src/test/usage.h
Normal file
42
src/test/usage.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(_WIN32)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
// Needs to be included after windows.h
|
||||
# include <psapi.h>
|
||||
#endif
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
namespace usage
|
||||
{
|
||||
void print_memory()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
PROCESS_MEMORY_COUNTERS_EX pmc;
|
||||
|
||||
if (!GetProcessMemoryInfo(
|
||||
GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc)))
|
||||
return;
|
||||
|
||||
std::cout << "Memory info:" << std::endl
|
||||
<< "\tPageFaultCount: " << pmc.PageFaultCount << std::endl
|
||||
<< "\tPeakWorkingSetSize: " << pmc.PeakWorkingSetSize << std::endl
|
||||
<< "\tWorkingSetSize: " << pmc.WorkingSetSize << std::endl
|
||||
<< "\tQuotaPeakPagedPoolUsage: " << pmc.QuotaPeakPagedPoolUsage
|
||||
<< std::endl
|
||||
<< "\tQuotaPagedPoolUsage: " << pmc.QuotaPagedPoolUsage
|
||||
<< std::endl
|
||||
<< "\tQuotaPeakNonPagedPoolUsage: "
|
||||
<< pmc.QuotaPeakNonPagedPoolUsage << std::endl
|
||||
<< "\tQuotaNonPagedPoolUsage: " << pmc.QuotaNonPagedPoolUsage
|
||||
<< std::endl
|
||||
<< "\tPagefileUsage: " << pmc.PagefileUsage << std::endl
|
||||
<< "\tPeakPagefileUsage: " << pmc.PeakPagefileUsage << std::endl
|
||||
<< "\tPrivateUsage: " << pmc.PrivateUsage << std::endl;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
71
src/test/xoroshiro.h
Normal file
71
src/test/xoroshiro.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace xoroshiro
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template<typename STATE, typename RESULT, STATE A, STATE B, STATE C>
|
||||
class XorOshiro
|
||||
{
|
||||
private:
|
||||
static constexpr unsigned STATE_BITS = 8 * sizeof(STATE);
|
||||
static constexpr unsigned RESULT_BITS = 8 * sizeof(RESULT);
|
||||
|
||||
static_assert(
|
||||
STATE_BITS >= RESULT_BITS,
|
||||
"STATE must have at least as many bits as RESULT");
|
||||
|
||||
STATE x;
|
||||
STATE y;
|
||||
|
||||
static inline STATE rotl(STATE x, STATE k)
|
||||
{
|
||||
return (x << k) | (x >> (STATE_BITS - k));
|
||||
}
|
||||
|
||||
public:
|
||||
XorOshiro(STATE x_ = 5489, STATE y_ = 0) : x(x_), y(y_)
|
||||
{
|
||||
// If both zero, then this does not work
|
||||
if (x_ == 0 && y_ == 0)
|
||||
abort();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
void set_state(STATE x_, STATE y_ = 0)
|
||||
{
|
||||
// If both zero, then this does not work
|
||||
if (x_ == 0 && y_ == 0)
|
||||
abort();
|
||||
|
||||
x = x_;
|
||||
y = y_;
|
||||
next();
|
||||
}
|
||||
|
||||
RESULT next()
|
||||
{
|
||||
STATE r = x + y;
|
||||
y ^= x;
|
||||
x = rotl(x, A) ^ y ^ (y << B);
|
||||
y = rotl(y, C);
|
||||
// If both zero, then this does not work
|
||||
if (x == 0 && y == 0)
|
||||
abort();
|
||||
return r >> (STATE_BITS - RESULT_BITS);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
using p128r64 = detail::XorOshiro<uint64_t, uint64_t, 55, 14, 36>;
|
||||
using p128r32 = detail::XorOshiro<uint64_t, uint32_t, 55, 14, 36>;
|
||||
using p64r32 = detail::XorOshiro<uint32_t, uint32_t, 27, 7, 20>;
|
||||
using p64r16 = detail::XorOshiro<uint32_t, uint16_t, 27, 7, 20>;
|
||||
using p32r16 = detail::XorOshiro<uint16_t, uint16_t, 13, 5, 10>;
|
||||
using p32r8 = detail::XorOshiro<uint16_t, uint8_t, 13, 5, 10>;
|
||||
using p16r8 = detail::XorOshiro<uint8_t, uint8_t, 4, 7, 3>;
|
||||
}
|
||||
Reference in New Issue
Block a user