File: astcenccli_platform_dependents.cpp

package info (click to toggle)
astc-encoder 5.2.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 43,352 kB
  • sloc: ansic: 44,622; cpp: 24,142; python: 3,403; sh: 78; makefile: 24
file content (309 lines) | stat: -rw-r--r-- 8,505 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// SPDX-License-Identifier: Apache-2.0
// ----------------------------------------------------------------------------
// Copyright 2011-2024 Arm Limited
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// ----------------------------------------------------------------------------

/**
 * @brief Platform-specific function implementations.
 *
 * This module contains functions with strongly OS-dependent implementations:
 *
 *  * CPU count queries
 *  * Threading
 *  * Time
 *
 * In addition to the basic thread abstraction (which is native pthreads on
 * all platforms, except Windows where it is an emulation of pthreads), a
 * utility function to create N threads and wait for them to complete a batch
 * task has also been provided.
 */

#include "astcenccli_internal.h"

/* ============================================================================
   Platform code for Windows using the Win32 APIs.
============================================================================ */
#if defined(_WIN32) && !defined(__CYGWIN__)

#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <Processthreadsapi.h>
#include <algorithm>
#include <cstring>

/** @brief Alias pthread_t to one of the internal Windows types. */
typedef HANDLE pthread_t;

/** @brief Alias pthread_attr_t to one of the internal Windows types. */
typedef int pthread_attr_t;

/**
 * @brief Proxy Windows @c CreateThread underneath a pthreads-like wrapper.
 */
static int pthread_create(
	pthread_t* thread,
	const pthread_attr_t* attribs,
	void* (*threadfunc)(void*),
	void* thread_arg
) {
	static_cast<void>(attribs);
	LPTHREAD_START_ROUTINE func = reinterpret_cast<LPTHREAD_START_ROUTINE>(threadfunc);
	*thread = CreateThread(nullptr, 0, func, thread_arg, 0, nullptr);

	// Ensure we return 0 on success, non-zero on error
	if (*thread == NULL)
	{
		return 1;
	}

	return 0;
}

/**
 * @brief Manually set CPU group and thread affinity.
 *
 * This is needed on Windows 10 or older to allow benefit from large core count
 * systems with more than 64 logical CPUs. The assignment is skipped on systems
 * with a single processor group, as it is not necessary.
 */
static void set_group_affinity(
	pthread_t thread,
	int thread_index
) {
	// Skip thread assignment for hardware with a single CPU group
	int group_count = GetActiveProcessorGroupCount();
	if (group_count == 1)
	{
		return;
	}

	// Ensure we have a valid assign if user creates more threads than cores
	int assign_index = thread_index % get_cpu_count();
	int assign_group { 0 };
	int assign_group_cpu_count { 0 };

	// Determine which core group and core in the group to use for this thread
	int group_cpu_count_sum { 0 };
	for (int group = 0; group < group_count; group++)
	{
		int group_cpu_count = static_cast<int>(GetMaximumProcessorCount(group));
		group_cpu_count_sum += group_cpu_count;

		if (assign_index < group_cpu_count_sum)
		{
			assign_group = group;
			assign_group_cpu_count = group_cpu_count;
			break;
		}
	}

	// Set the affinity to the assigned group, and all supported cores
	GROUP_AFFINITY affinity {};
	affinity.Mask = (1 << assign_group_cpu_count) - 1;
	affinity.Group = assign_group;
	SetThreadGroupAffinity(thread, &affinity, nullptr);
}

/**
 * @brief Proxy Windows @c WaitForSingleObject underneath a pthreads-like wrapper.
 */
static int pthread_join(
	pthread_t thread,
	void** value
) {
	static_cast<void>(value);
	WaitForSingleObject(thread, INFINITE);
	return 0;
}

/* See header for documentation */
int get_cpu_count()
{
	DWORD cpu_count = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
	return static_cast<int>(cpu_count);
}

/* See header for documentation */
double get_time()
{
	FILETIME tv;
	GetSystemTimePreciseAsFileTime(&tv);
	unsigned long long ticks = tv.dwHighDateTime;
	ticks = (ticks << 32) | tv.dwLowDateTime;
	return static_cast<double>(ticks) / 1.0e7;
}

/* See header for documentation */
void set_thread_name(
	const char* name
) {
	// Names are limited to 16 characters
	wchar_t wname [16] { 0 };
	size_t name_len = std::strlen(name);
	size_t clamp_len = std::min<size_t>(name_len, 15);

	// We know we only have basic 7-bit ASCII so just widen
	for (size_t i = 0; i < clamp_len; i++)
	{
		wname[i] = static_cast<wchar_t>(name[i]);
	}

 	SetThreadDescription(GetCurrentThread(), wname);
}

/* ============================================================================
   Platform code for an platform using POSIX APIs.
============================================================================ */
#else

#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>

/* See header for documentation */
int get_cpu_count()
{
	return static_cast<int>(sysconf(_SC_NPROCESSORS_ONLN));
}

/* See header for documentation */
double get_time()
{
	timeval tv;
	gettimeofday(&tv, 0);
	return static_cast<double>(tv.tv_sec) + static_cast<double>(tv.tv_usec) * 1.0e-6;
}

/* See header for documentation */
void set_thread_name(
	const char* name
) {
	// No standard mechanism, so be defensive here
#if defined(__linux__)
	pthread_setname_np(pthread_self(), name);
#elif defined(__APPLE__)
	pthread_setname_np(name);
#else
	(void)name;
#endif
}

#endif

/**
 * @brief Worker thread helper payload for launch_threads.
 */
struct launch_desc
{
	/** @brief The native thread handle. */
	pthread_t thread_handle;
	/** @brief The total number of threads in the thread pool. */
	int thread_count;
	/** @brief The thread index in the thread pool. */
	int thread_id;
	/** @brief The user thread function to execute. */
	void (*func)(int, int, void*);
	/** @brief The user thread payload. */
	void* payload;
};

/**
 * @brief Helper function to translate thread entry points.
 *
 * Convert a (void*) thread entry to an (int, void*) thread entry, where the
 * integer contains the thread ID in the thread pool.
 *
 * @param p The thread launch helper payload.
 */
static void* launch_threads_helper(
	void *p
) {
	launch_desc* ltd = reinterpret_cast<launch_desc*>(p);
	ltd->func(ltd->thread_count, ltd->thread_id, ltd->payload);
	return nullptr;
}

/* See header for documentation */
void launch_threads(
	const char* operation,
	int thread_count,
	void (*func)(int, int, void*),
	void *payload
) {
	// Directly execute single threaded workloads on this thread
	if (thread_count <= 1)
	{
		func(1, 0, payload);
		return;
	}

	// Otherwise spawn worker threads
	launch_desc *thread_descs = new launch_desc[thread_count];
	int actual_thread_count { 0 };

	for (int i = 0; i < thread_count; i++)
	{
		thread_descs[actual_thread_count].thread_count = thread_count;
		thread_descs[actual_thread_count].thread_id = actual_thread_count;
		thread_descs[actual_thread_count].payload = payload;
		thread_descs[actual_thread_count].func = func;

		// Handle pthread_create failing by simply using fewer threads
		int error = pthread_create(
			&(thread_descs[actual_thread_count].thread_handle),
			nullptr,
			launch_threads_helper,
			reinterpret_cast<void*>(thread_descs + actual_thread_count));

		// Track how many threads we actually created
		if (!error)
		{
			// Windows needs explicit thread assignment to handle large core count systems
			#if defined(_WIN32) && !defined(__CYGWIN__)
				set_group_affinity(
					thread_descs[actual_thread_count].thread_handle,
					actual_thread_count);
			#endif

			actual_thread_count++;
		}
	}

	// If we did not create thread_count threads then emit a warning
	if (actual_thread_count != thread_count)
	{
		int log_count = actual_thread_count == 0 ? 1 : actual_thread_count;
		const char* log_s = log_count == 1 ? "" : "s";
		printf("WARNING: %s using %d thread%s due to thread creation error\n\n",
		       operation, log_count, log_s);
	}

	// If we managed to spawn any threads wait for them to complete
	if (actual_thread_count != 0)
	{
		for (int i = 0; i < actual_thread_count; i++)
		{
			pthread_join(thread_descs[i].thread_handle, nullptr);
		}
	}
	// Else fall back to using this thread
	else
	{
		func(1, 0, payload);
	}

	delete[] thread_descs;
}