2020-05-08 14:11:19 +00:00
# include "getNumberOfPhysicalCPUCores.h"
2023-06-27 11:54:01 +00:00
# include <filesystem>
2020-04-16 12:31:57 +00:00
2022-09-28 08:45:15 +00:00
# include "config.h"
2021-12-31 08:39:59 +00:00
# if defined(OS_LINUX)
# include <cmath>
# include <fstream>
# endif
2016-01-13 02:11:40 +00:00
2023-01-06 19:55:55 +00:00
# include <boost/algorithm/string/trim.hpp>
2023-06-27 11:54:01 +00:00
# include <boost/algorithm/string/split.hpp>
# include <base/range.h>
2023-01-06 19:55:55 +00:00
2020-05-08 14:11:19 +00:00
# include <thread>
2023-01-06 19:55:55 +00:00
# include <set>
2020-05-08 14:11:19 +00:00
2023-01-24 12:57:56 +00:00
namespace
{
2023-01-24 09:57:12 +00:00
2021-12-31 08:39:59 +00:00
# if defined(OS_LINUX)
2023-06-27 11:54:01 +00:00
int32_t readFrom ( const std : : filesystem : : path & filename , int default_value )
2021-12-31 08:39:59 +00:00
{
2022-04-15 20:54:08 +00:00
std : : ifstream infile ( filename ) ;
if ( ! infile . is_open ( ) )
return default_value ;
int idata ;
if ( infile > > idata )
return idata ;
else
return default_value ;
}
2021-12-31 08:39:59 +00:00
2022-04-15 20:54:08 +00:00
/// Try to look at cgroups limit if it is available.
2023-01-24 09:57:12 +00:00
uint32_t getCGroupLimitedCPUCores ( unsigned default_cpu_count )
2022-04-15 20:54:08 +00:00
{
2022-09-10 02:07:51 +00:00
uint32_t quota_count = default_cpu_count ;
2023-06-27 11:54:01 +00:00
std : : filesystem : : path prefix = " /sys/fs/cgroup " ;
/// cgroupsv2
std : : ifstream contr_file ( prefix / " cgroup.controllers " ) ;
if ( contr_file . is_open ( ) )
{
/// First, we identify the cgroup the process belongs
std : : ifstream cgroup_name_file ( " /proc/self/cgroup " ) ;
if ( ! cgroup_name_file . is_open ( ) )
return default_cpu_count ;
// cgroup_name_file always starts with '0::/' for v2
cgroup_name_file . ignore ( 4 ) ;
std : : string cgroup_name ;
cgroup_name_file > > cgroup_name ;
std : : filesystem : : path current_cgroup ;
if ( cgroup_name . empty ( ) )
current_cgroup = prefix ;
else
current_cgroup = prefix / cgroup_name ;
// Looking for cpu.max in directories from the current cgroup to the top level
// It does not stop on the first time since the child could have a greater value than parent
while ( current_cgroup ! = prefix . parent_path ( ) )
{
std : : ifstream cpu_max_file ( current_cgroup / " cpu.max " ) ;
current_cgroup = current_cgroup . parent_path ( ) ;
if ( cpu_max_file . is_open ( ) )
{
std : : string cpu_limit_str ;
float cpu_period ;
cpu_max_file > > cpu_limit_str > > cpu_period ;
if ( cpu_limit_str ! = " max " & & cpu_period ! = 0 )
{
float cpu_limit = std : : stof ( cpu_limit_str ) ;
quota_count = std : : min ( static_cast < uint32_t > ( ceil ( cpu_limit / cpu_period ) ) , quota_count ) ;
}
}
}
current_cgroup = prefix / cgroup_name ;
// Looking for cpuset.cpus.effective in directories from the current cgroup to the top level
while ( current_cgroup ! = prefix . parent_path ( ) )
{
std : : ifstream cpuset_cpus_file ( current_cgroup / " cpuset.cpus.effective " ) ;
current_cgroup = current_cgroup . parent_path ( ) ;
if ( cpuset_cpus_file . is_open ( ) )
{
// The line in the file is "0,2-4,6,9-14" cpu numbers
// It's always grouped and ordered
std : : vector < std : : string > cpu_ranges ;
std : : string cpuset_line ;
cpuset_cpus_file > > cpuset_line ;
if ( cpuset_line . empty ( ) )
continue ;
boost : : split ( cpu_ranges , cpuset_line , boost : : is_any_of ( " , " ) ) ;
uint32_t cpus_count = 0 ;
for ( const std : : string & cpu_number_or_range : cpu_ranges )
{
std : : vector < std : : string > cpu_range ;
boost : : split ( cpu_range , cpu_number_or_range , boost : : is_any_of ( " - " ) ) ;
if ( cpu_range . size ( ) = = 2 )
{
int start = std : : stoi ( cpu_range [ 0 ] ) ;
int end = std : : stoi ( cpu_range [ 1 ] ) ;
cpus_count + = ( end - start ) + 1 ;
}
else
cpus_count + + ;
}
quota_count = std : : min ( cpus_count , quota_count ) ;
break ;
}
}
return quota_count ;
}
/// cgroupsv1
2022-04-15 20:54:08 +00:00
/// Return the number of milliseconds per period process is guaranteed to run.
/// -1 for no quota
2023-06-27 11:54:01 +00:00
int cgroup_quota = readFrom ( prefix / " cpu/cpu.cfs_quota_us " , - 1 ) ;
int cgroup_period = readFrom ( prefix / " cpu/cpu.cfs_period_us " , - 1 ) ;
2021-12-31 08:39:59 +00:00
if ( cgroup_quota > - 1 & & cgroup_period > 0 )
2022-09-10 02:07:51 +00:00
quota_count = static_cast < uint32_t > ( ceil ( static_cast < float > ( cgroup_quota ) / static_cast < float > ( cgroup_period ) ) ) ;
2021-12-31 08:39:59 +00:00
2022-03-31 15:21:54 +00:00
return std : : min ( default_cpu_count , quota_count ) ;
2021-12-31 08:39:59 +00:00
}
2023-01-22 16:23:35 +00:00
# endif
2023-01-06 19:55:55 +00:00
2023-01-22 16:23:35 +00:00
/// Returns number of physical cores, unlike std::thread::hardware_concurrency() which returns the logical core count. With 2-way SMT
/// (HyperThreading) enabled, physical_concurrency() returns half of of std::thread::hardware_concurrency(), otherwise return the same.
2023-01-24 09:57:12 +00:00
# if defined(__x86_64__) && defined(OS_LINUX)
unsigned physical_concurrency ( )
2023-01-06 19:55:55 +00:00
try
{
2023-01-22 16:23:35 +00:00
/// The CPUID instruction isn't reliable across different vendors and CPU models. The best option to get the physical core count is
/// to parse /proc/cpuinfo. boost::thread::physical_concurrency() does the same, so use their implementation.
///
/// See https://doc.callmematthi.eu/static/webArticles/Understanding%20Linux%20_proc_cpuinfo.pdf
2023-01-06 19:55:55 +00:00
std : : ifstream proc_cpuinfo ( " /proc/cpuinfo " ) ;
2023-01-29 20:37:19 +00:00
if ( ! proc_cpuinfo . is_open ( ) )
2023-01-29 17:25:08 +00:00
/// In obscure cases (chroot) /proc can be unmounted
return std : : thread : : hardware_concurrency ( ) ;
2023-01-22 16:23:35 +00:00
using CoreEntry = std : : pair < size_t , size_t > ; /// physical id, core id
using CoreEntrySet = std : : set < CoreEntry > ;
CoreEntrySet core_entries ;
2023-01-06 19:55:55 +00:00
CoreEntry cur_core_entry ;
std : : string line ;
while ( std : : getline ( proc_cpuinfo , line ) )
{
size_t pos = line . find ( std : : string ( " : " ) ) ;
if ( pos = = std : : string : : npos )
continue ;
std : : string key = line . substr ( 0 , pos ) ;
std : : string val = line . substr ( pos + 1 ) ;
2023-01-22 16:23:35 +00:00
if ( key . find ( " physical id " ) ! = std : : string : : npos )
2023-01-06 19:55:55 +00:00
{
cur_core_entry . first = std : : stoi ( val ) ;
continue ;
}
2023-01-22 16:23:35 +00:00
if ( key . find ( " core id " ) ! = std : : string : : npos )
2023-01-06 19:55:55 +00:00
{
cur_core_entry . second = std : : stoi ( val ) ;
core_entries . insert ( cur_core_entry ) ;
continue ;
}
}
2023-01-22 16:23:35 +00:00
return core_entries . empty ( ) ? /*unexpected format*/ std : : thread : : hardware_concurrency ( ) : static_cast < unsigned > ( core_entries . size ( ) ) ;
2023-01-06 19:55:55 +00:00
}
catch ( . . . )
{
2023-01-22 16:23:35 +00:00
return std : : thread : : hardware_concurrency ( ) ; /// parsing error
}
2022-04-15 20:54:08 +00:00
# endif
2016-01-13 02:11:40 +00:00
2023-01-24 09:57:12 +00:00
unsigned getNumberOfPhysicalCPUCoresImpl ( )
2016-01-13 02:11:40 +00:00
{
2023-01-22 16:23:35 +00:00
unsigned cpu_count = std : : thread : : hardware_concurrency ( ) ; /// logical cores (with SMT/HyperThreading)
2023-01-06 19:55:55 +00:00
2023-01-22 16:23:35 +00:00
/// Most x86_64 CPUs have 2-way SMT (Hyper-Threading).
2023-01-06 19:55:55 +00:00
/// Aarch64 and RISC-V don't have SMT so far.
2023-01-22 16:23:35 +00:00
/// POWER has SMT and it can be multi-way (e.g. 8-way), but we don't know how ClickHouse really behaves, so use all of them.
2023-01-06 19:55:55 +00:00
2023-01-23 19:48:09 +00:00
# if defined(__x86_64__) && defined(OS_LINUX)
2023-01-22 16:23:35 +00:00
/// On really big machines, SMT is detrimental to performance (+ ~5% overhead in ClickBench). On such machines, we limit ourself to the physical cores.
/// Few cores indicate it is a small machine, runs in a VM or is a limited cloud instance --> it is reasonable to use all the cores.
if ( cpu_count > = 32 )
cpu_count = physical_concurrency ( ) ;
2023-01-06 19:55:55 +00:00
# endif
2020-07-16 23:12:47 +00:00
2021-12-31 08:39:59 +00:00
# if defined(OS_LINUX)
2022-04-15 20:54:08 +00:00
cpu_count = getCGroupLimitedCPUCores ( cpu_count ) ;
# endif
return cpu_count ;
2016-01-13 02:11:40 +00:00
}
2022-04-15 20:56:51 +00:00
2023-01-24 09:57:12 +00:00
}
2022-04-15 20:56:51 +00:00
unsigned getNumberOfPhysicalCPUCores ( )
{
/// Calculate once.
static auto res = getNumberOfPhysicalCPUCoresImpl ( ) ;
return res ;
}