X-Git-Url: http://git.rrze.uni-erlangen.de/gitweb/?p=LbmBenchmarkKernelsPublic.git;a=blobdiff_plain;f=src%2FPinning.c;fp=src%2FPinning.c;h=0cf70eafbddd61e8aa2968f544a1d1f499ad6eb0;hp=0000000000000000000000000000000000000000;hb=109880839321408644c94a34eb31208460b9f46d;hpb=42cf91486fb5c1ad178b3d21935a1be563e5fa39 diff --git a/src/Pinning.c b/src/Pinning.c new file mode 100644 index 0000000..0cf70ea --- /dev/null +++ b/src/Pinning.c @@ -0,0 +1,393 @@ +// -------------------------------------------------------------------------- +// +// Copyright +// Markus Wittmann, 2016-2017 +// RRZE, University of Erlangen-Nuremberg, Germany +// markus.wittmann -at- fau.de or hpc -at- rrze.fau.de +// +// Viktor Haag, 2016 +// LSS, University of Erlangen-Nuremberg, Germany +// +// This file is part of the Lattice Boltzmann Benchmark Kernels (LbmBenchKernels). +// +// LbmBenchKernels is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// LbmBenchKernels is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with LbmBenchKernels. If not, see . +// +// -------------------------------------------------------------------------- +#ifndef _GNU_SOURCE + #define _GNU_SOURCE +#endif +#include +#include + + +#include "Base.h" +#include "Pinning.h" + + + + +// ----------------------------------------------------------------------- +// +// Binds the calling thread to specified core. +// +// Return value: 0 = success, else error. +// +// ----------------------------------------------------------------------- + +int PinCurrentThreadToCore(int coreNumber) +{ + int error = 0; + + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + CPU_SET(coreNumber, &cpu_set); + + error = sched_setaffinity((pid_t)0, sizeof(cpu_set_t), &cpu_set); + + if (error != 0) { + Error("pinning thread to core %d failed (%d): %s\n", + coreNumber, error, strerror(error)); + } + + return error; +} + + +// ----------------------------------------------------------------------- +// +// Binds the calling thread to specified core by a cpu list specified +// in the given environment variable. +// +// Return value: 0 = success, else error. +// +// ----------------------------------------------------------------------- + +int PinCurrentThreadByEnvVar(const char * envVarName, + int mpiRank, int nodeRank, int threadNumber) +{ + const char * envVarValue; + int core; + + envVarValue = getenv(envVarName); + + if (envVarValue == NULL) { + if (mpiRank == 0) { + Print("skip pinning: env var %s not set\n", envVarName); + } + + return 0; + } + + core = PinParseCpuList(envVarValue, mpiRank, nodeRank, threadNumber); + + if (core < 0) { + return core; + } + + return PinCurrentThreadToCore(core); +} + + +// ----------------------------------------------------------------------- +// +// Binds the calling thread to a core specified in the CPU list. +// +// Return value: 0 = success, else error. +// +// ----------------------------------------------------------------------- + +int PinCurrentThreadByCpuList(const char * cpuList, + int mpiRank, int nodeRank, int threadNumber) +{ + int core; + + if (cpuList == NULL) { + if (mpiRank == 0) { + printf("ERROR: cpu list is NULL.\n"); + } + + exit(1); + } + + core = PinParseCpuList(cpuList, mpiRank, nodeRank, threadNumber); + + if (core < 0) { + return core; + } + + return PinCurrentThreadToCore(core); +} + + +// ----------------------------------------------------------------------- +// +// Parses the provided cpu list and returns the core number for the +// specified MPI rank, local rank, and thread. +// +// The cpu list has for example a format of: 0,1,2 or 0,1,2_3,4,5 +// +// Blocks (0,1,2 or 3,4,5) separated by "_" specify pinning inside a +// node rank. The first block maps to node rank 1, the second to node +// rank 2, etc. +// +// Inside a block the core numbers specify where the threads should +// be pinned to. They are separated by "," and the first number maps +// to the first core, the second number to the second core, etc. +// +// For example: 0,2,4_6,8,10 +// +// Node rank 0 thread 0 pinned to core 0 +// 0 1 2 +// 0 2 4 +// 1 0 6 +// 1 1 8 +// 1 2 10 +// +// ----------------------------------------------------------------------- + +int PinParseCpuList(const char * cpuList, + int mpiRank, int nodeRank, int threadNumber) +{ + int cpu = -1; + + if (cpuList == NULL) { + return -1; + } + + const char * c = cpuList; + + // Ensure only valid characters are in the cpu list. + // Cpu list is in the format of "0,1,2_3,4,5". + while (((*c >= '0' && *c <= '9') || *c == ',' || *c == '_')) { + ++c; + } + + if (*c != 0x00) { + // Invalid character detected. + return -2; + } + + c = cpuList; + + int i = 0; + + // Move variable c after the "nodeRank"th "_" in the cpu list. + while (i < nodeRank && *c != 0x00) { + if (*c == '_') ++i; + ++c; + } + + if (i != nodeRank || *c < '0' || *c > '9') { + // Cpu list for this node rank not found. + return -3; + } + + // Now find the core for the specified thread. + + int t = 0; + + while (t < threadNumber && *c != 0x00) { + if (*c == ',') { + ++t; + } + else if (*c == '_') { + // Unexpected character at this position. + break; + } + + ++c; + } + + if (t != threadNumber || *c < '0' || *c > '9') { + // Cpu for this threadNumber not found. + return -4; + } + + cpu = atoi(c); + + return cpu; +} + + + +// ----------------------------------------------------------------------- +// +// Returns the first core from the calling thread's affinity set. +// +// On error a value < 0 is returned. +// +// ----------------------------------------------------------------------- + +int PinCurrentCore() +{ + int core = -1; + int err; + + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + + err = sched_getaffinity((pid_t)0, sizeof(cpu_set_t), &cpu_set); + + // constant CPU_SETSIZE is one larger than the maximum CPU + // number that can be stored in a CPU set + for (int i = 0; i < CPU_SETSIZE; ++i) { + if (CPU_ISSET(i, &cpu_set)) { + core = i; + break; + } + } + + if (err != 0) { + Error("getting thread affinty failed (%d): %s\n", err, strerror(err)); + return -1; + } + + return core; +} + + + +// ----------------------------------------------------------------------- +// +// Returns the all cores from the calling thread's affinity set. +// +// On error a value < 0 is returned. +// +// ----------------------------------------------------------------------- + +typedef cpu_set_t CpuSet; + + +static CpuSet PinCurrentCores() +{ + CpuSet cpuSet; + int err; + + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + + err = sched_getaffinity((pid_t)0, sizeof(cpu_set_t), &cpu_set); + + cpuSet = cpu_set; + + if (err != 0) { + Error("getting thread affinty failed (%d): %s\n", err, strerror(err)); + return cpuSet; + } + + return cpuSet; +} + +static char * CpuSetToString(cpu_set_t * cpu_set) +{ + int previousSetCore = -2; + int rangeBeginCore = -2; + + char * buffer1 = (char *)malloc(1024); + Assert(buffer1 != NULL); + char * buffer2 = (char *)malloc(1024); + Assert(buffer2 != NULL); + + buffer1[0] = 0x00; + buffer2[0] = 0x00; + + char * buffer = buffer1; + char * bufferOld = buffer2; + + const char * empty = ""; + const char * realComma = ","; + const char * comma = empty; + + // TODO: use snprintf + // TODO: increase allocated buffer if necessary + + for (int i = 0; i < CPU_SETSIZE; ++i) { + if (!CPU_ISSET(i, cpu_set)) { + continue; + } + + if (i == previousSetCore + 1) { + previousSetCore = i; + continue; + } + + // Now we reached the end of a range. + // The range can also consist of only one core. + // Be aware, that this core is not part of the range. + + // TODO: this code is repeated below -> use it only once + if (rangeBeginCore >= 0 && previousSetCore >= 0) { + char * tmp; + + tmp = buffer; + buffer = bufferOld; + bufferOld = tmp; + + if (rangeBeginCore < previousSetCore) { + sprintf(buffer, "%s%s%d-%d", bufferOld, comma, rangeBeginCore, previousSetCore); + } + else { + sprintf(buffer, "%s%s%d", bufferOld, comma, previousSetCore); + } + + comma = realComma; + } + + // With this core a new range begins. + rangeBeginCore = i; + previousSetCore = i; + } + + if (rangeBeginCore >= 0 && previousSetCore >= 0) { + char * tmp; + + tmp = buffer; + buffer = bufferOld; + bufferOld = tmp; + + if (rangeBeginCore < previousSetCore) { + sprintf(buffer, "%s%s%d-%d", bufferOld, comma, rangeBeginCore, previousSetCore); + } + else { + sprintf(buffer, "%s%s%d", bufferOld, comma, previousSetCore); + } + } + + free(bufferOld); bufferOld = NULL; + + return buffer; +} + +char * PinCpuListAsString() +{ + CpuSet cpuSet = PinCurrentCores(); + + return CpuSetToString(&cpuSet); +} + +#ifdef TEST + +int main(int argc, char * argv[]) +{ + char * cpuList = PinCpuListAsString(); + + printf("pinned to cores: %s\n", cpuList); + + free(cpuList); cpuList = NULL; + + return 0; +} + +#endif // TEST +