447 lines
10 KiB
C
447 lines
10 KiB
C
/* Basic functions to support threading.
|
|
*
|
|
* 29/9/22
|
|
* - from threadpool.c
|
|
*/
|
|
|
|
/*
|
|
|
|
This file is part of VIPS.
|
|
|
|
VIPS is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301 USA
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
|
|
|
|
*/
|
|
|
|
/*
|
|
#define VIPS_DEBUG
|
|
#define VIPS_DEBUG_RED
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif /*HAVE_CONFIG_H*/
|
|
#include <glib/gi18n-lib.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif /*HAVE_UNISTD_H*/
|
|
#include <errno.h>
|
|
|
|
#include <vips/vips.h>
|
|
#include <vips/internal.h>
|
|
#include <vips/thread.h>
|
|
#include <vips/debug.h>
|
|
|
|
#ifdef G_OS_WIN32
|
|
#include <windows.h>
|
|
#endif /*G_OS_WIN32*/
|
|
|
|
/* Maximum value we allow for VIPS_CONCURRENCY. We need to stop huge values
|
|
* killing the system.
|
|
*/
|
|
#define MAX_THREADS (1024)
|
|
|
|
/* Default n threads ... 0 means get from environment.
|
|
*/
|
|
int vips__concurrency = 0;
|
|
|
|
/* Default tile geometry ... can be set by vips_init().
|
|
*/
|
|
int vips__tile_width = VIPS__TILE_WIDTH;
|
|
int vips__tile_height = VIPS__TILE_HEIGHT;
|
|
int vips__fatstrip_height = VIPS__FATSTRIP_HEIGHT;
|
|
int vips__thinstrip_height = VIPS__THINSTRIP_HEIGHT;
|
|
|
|
/* Set this GPrivate to indicate that is a libvips thread.
|
|
*/
|
|
static GPrivate *is_vips_thread_key = NULL;
|
|
|
|
/* TRUE if we are a vips thread. We sometimes manage resource allocation
|
|
* differently for vips threads since we can cheaply free stuff on thread
|
|
* termination.
|
|
*/
|
|
gboolean
|
|
vips_thread_isvips( void )
|
|
{
|
|
return( g_private_get( is_vips_thread_key ) != NULL );
|
|
}
|
|
|
|
/* Glib 2.32 revised the thread API. We need some compat functions.
|
|
*/
|
|
|
|
GMutex *
|
|
vips_g_mutex_new( void )
|
|
{
|
|
GMutex *mutex;
|
|
|
|
mutex = g_new( GMutex, 1 );
|
|
g_mutex_init( mutex );
|
|
|
|
return( mutex );
|
|
}
|
|
|
|
void
|
|
vips_g_mutex_free( GMutex *mutex )
|
|
{
|
|
g_mutex_clear( mutex );
|
|
g_free( mutex );
|
|
}
|
|
|
|
GCond *
|
|
vips_g_cond_new( void )
|
|
{
|
|
GCond *cond;
|
|
|
|
cond = g_new( GCond, 1 );
|
|
g_cond_init( cond );
|
|
|
|
return( cond );
|
|
}
|
|
|
|
void
|
|
vips_g_cond_free( GCond *cond )
|
|
{
|
|
g_cond_clear( cond );
|
|
g_free( cond );
|
|
}
|
|
|
|
typedef struct {
|
|
const char *domain;
|
|
GThreadFunc func;
|
|
gpointer data;
|
|
} VipsThreadInfo;
|
|
|
|
static void *
|
|
vips_thread_run( gpointer data )
|
|
{
|
|
VipsThreadInfo *info = (VipsThreadInfo *) data;
|
|
|
|
void *result;
|
|
|
|
/* Set this to something (anything) to tag this thread as a vips
|
|
* worker. No need to call g_private_replace as there is no
|
|
* GDestroyNotify handler associated with a worker.
|
|
*/
|
|
g_private_set( is_vips_thread_key, info );
|
|
|
|
result = info->func( info->data );
|
|
|
|
g_free( info );
|
|
|
|
vips_thread_shutdown();
|
|
|
|
return( result );
|
|
}
|
|
|
|
GThread *
|
|
vips_g_thread_new( const char *domain, GThreadFunc func, gpointer data )
|
|
{
|
|
GThread *thread;
|
|
VipsThreadInfo *info;
|
|
GError *error = NULL;
|
|
|
|
info = g_new( VipsThreadInfo, 1 );
|
|
info->domain = domain;
|
|
info->func = func;
|
|
info->data = data;
|
|
|
|
thread = g_thread_try_new( domain, vips_thread_run, info, &error );
|
|
|
|
VIPS_DEBUG_MSG_RED( "vips_g_thread_new: g_thread_create( %s ) = %p\n",
|
|
domain, thread );
|
|
|
|
if( !thread ) {
|
|
if( error )
|
|
vips_g_error( &error );
|
|
else
|
|
vips_error( domain,
|
|
"%s", _( "unable to create thread" ) );
|
|
}
|
|
|
|
return( thread );
|
|
}
|
|
|
|
static int
|
|
get_num_processors( void )
|
|
{
|
|
#if GLIB_CHECK_VERSION( 2, 48, 1 )
|
|
/* We could use g_get_num_processors when GLib >= 2.48.1, see:
|
|
* https://gitlab.gnome.org/GNOME/glib/commit/999711abc82ea3a698d05977f9f91c0b73957f7f
|
|
* https://gitlab.gnome.org/GNOME/glib/commit/2149b29468bb99af3c29d5de61f75aad735082dc
|
|
*/
|
|
return( g_get_num_processors() );
|
|
#else
|
|
int nproc;
|
|
|
|
nproc = 1;
|
|
|
|
#ifdef G_OS_UNIX
|
|
|
|
#if defined(HAVE_UNISTD_H) && defined(_SC_NPROCESSORS_ONLN)
|
|
{
|
|
/* POSIX style.
|
|
*/
|
|
int x;
|
|
|
|
x = sysconf( _SC_NPROCESSORS_ONLN );
|
|
if( x > 0 )
|
|
nproc = x;
|
|
}
|
|
#elif defined HW_NCPU
|
|
{
|
|
/* BSD style.
|
|
*/
|
|
int x;
|
|
size_t len = sizeof(x);
|
|
|
|
sysctl( (int[2]) {CTL_HW, HW_NCPU}, 2, &x, &len, NULL, 0 );
|
|
if( x > 0 )
|
|
nproc = x;
|
|
}
|
|
#endif
|
|
|
|
/* libgomp has some very complex code on Linux to count the number of
|
|
* processors available to the current process taking pthread affinity
|
|
* into account, but we don't attempt that here. Perhaps we should?
|
|
*/
|
|
|
|
#endif /*G_OS_UNIX*/
|
|
|
|
#ifdef G_OS_WIN32
|
|
{
|
|
/* Count the CPUs currently available to this process.
|
|
*/
|
|
SYSTEM_INFO sysinfo;
|
|
DWORD_PTR process_cpus;
|
|
DWORD_PTR system_cpus;
|
|
|
|
/* This *never* fails, use it as fallback
|
|
*/
|
|
GetNativeSystemInfo( &sysinfo );
|
|
nproc = (int) sysinfo.dwNumberOfProcessors;
|
|
|
|
if( GetProcessAffinityMask( GetCurrentProcess(),
|
|
&process_cpus, &system_cpus ) ) {
|
|
unsigned int af_count;
|
|
|
|
for( af_count = 0; process_cpus != 0; process_cpus >>= 1 )
|
|
if( process_cpus & 1 )
|
|
af_count++;
|
|
|
|
/* Prefer affinity-based result, if available
|
|
*/
|
|
if( af_count > 0 )
|
|
nproc = af_count;
|
|
}
|
|
}
|
|
#endif /*G_OS_WIN32*/
|
|
|
|
return( nproc );
|
|
#endif /*!GLIB_CHECK_VERSION( 2, 48, 1 )*/
|
|
}
|
|
|
|
/* The default concurrency, set by the environment variable VIPS_CONCURRENCY,
|
|
* or if that is not set, the number of threads available on the host machine.
|
|
*/
|
|
static int
|
|
vips__concurrency_get_default( void )
|
|
{
|
|
const char *str;
|
|
int nthr;
|
|
int x;
|
|
|
|
/* Tell the threads system how much concurrency we expect.
|
|
*/
|
|
if( vips__concurrency > 0 )
|
|
nthr = vips__concurrency;
|
|
else if( ((str = g_getenv( "VIPS_CONCURRENCY" ))
|
|
#if ENABLE_DEPRECATED
|
|
|| (str = g_getenv( "IM_CONCURRENCY" ))
|
|
#endif
|
|
) && (x = atoi( str )) > 0 )
|
|
nthr = x;
|
|
else
|
|
nthr = get_num_processors();
|
|
|
|
if( nthr < 1 ||
|
|
nthr > MAX_THREADS ) {
|
|
nthr = VIPS_CLIP( 1, nthr, MAX_THREADS );
|
|
|
|
g_warning( _( "threads clipped to %d" ), nthr );
|
|
}
|
|
|
|
return( nthr );
|
|
}
|
|
|
|
/**
|
|
* vips_concurrency_set:
|
|
* @concurrency: number of threads to run
|
|
*
|
|
* Sets the number of worker threads that vips should use when running a
|
|
* #VipsThreadPool.
|
|
*
|
|
* The special value 0 means "default". In this case, the number of threads
|
|
* is set by the environment variable VIPS_CONCURRENCY, or if that is not
|
|
* set, the number of threads available on the host machine.
|
|
*
|
|
* See also: vips_concurrency_get().
|
|
*/
|
|
void
|
|
vips_concurrency_set( int concurrency )
|
|
{
|
|
/* Tell the threads system how much concurrency we expect.
|
|
*/
|
|
if( concurrency < 1 )
|
|
concurrency = vips__concurrency_get_default();
|
|
else if( concurrency > MAX_THREADS ) {
|
|
concurrency = MAX_THREADS;
|
|
|
|
g_warning( _( "threads clipped to %d" ), MAX_THREADS );
|
|
}
|
|
|
|
vips__concurrency = concurrency;
|
|
}
|
|
|
|
/**
|
|
* vips_concurrency_get:
|
|
*
|
|
* Returns the number of worker threads that vips should use when running a
|
|
* #VipsThreadPool.
|
|
*
|
|
* vips gets this values from these sources in turn:
|
|
*
|
|
* If vips_concurrency_set() has been called, this value is used. The special
|
|
* value 0 means "default". You can also use the command-line argument
|
|
* "--vips-concurrency" to set this value.
|
|
*
|
|
* If vips_concurrency_set() has not been called and no command-line argument
|
|
* was used, vips uses the value of the environment variable VIPS_CONCURRENCY,
|
|
*
|
|
* If VIPS_CONCURRENCY has not been set, vips finds the number of hardware
|
|
* threads that the host machine can run in parallel and uses that value.
|
|
*
|
|
* The final value is clipped to the range 1 - 1024.
|
|
*
|
|
* See also: vips_concurrency_get().
|
|
*
|
|
* Returns: number of worker threads to use.
|
|
*/
|
|
int
|
|
vips_concurrency_get( void )
|
|
{
|
|
return( vips__concurrency );
|
|
}
|
|
|
|
/**
|
|
* vips_get_tile_size: (method)
|
|
* @im: image to guess for
|
|
* @tile_width: (out): return selected tile width
|
|
* @tile_height: (out): return selected tile height
|
|
* @n_lines: (out): return buffer height in scanlines
|
|
*
|
|
* Pick a tile size and a buffer height for this image and the current
|
|
* value of vips_concurrency_get(). The buffer height
|
|
* will always be a multiple of tile_height.
|
|
*
|
|
* The buffer height is the height of each buffer we fill in sink disc. Since
|
|
* we have two buffers, the largest range of input locality is twice the output
|
|
* buffer size, plus whatever margin we add for things like convolution.
|
|
*/
|
|
void
|
|
vips_get_tile_size( VipsImage *im,
|
|
int *tile_width, int *tile_height, int *n_lines )
|
|
{
|
|
const int nthr = vips_concurrency_get();
|
|
const int typical_image_width = 1000;
|
|
|
|
/* Compiler warnings.
|
|
*/
|
|
*tile_width = 1;
|
|
*tile_height = 1;
|
|
|
|
/* Pick a render geometry.
|
|
*/
|
|
switch( im->dhint ) {
|
|
case VIPS_DEMAND_STYLE_SMALLTILE:
|
|
*tile_width = vips__tile_width;
|
|
*tile_height = vips__tile_height;
|
|
break;
|
|
|
|
case VIPS_DEMAND_STYLE_ANY:
|
|
case VIPS_DEMAND_STYLE_FATSTRIP:
|
|
*tile_width = im->Xsize;
|
|
*tile_height = vips__fatstrip_height;
|
|
break;
|
|
|
|
case VIPS_DEMAND_STYLE_THINSTRIP:
|
|
*tile_width = im->Xsize;
|
|
/* Only enable thinstrip height for very wide images -- the
|
|
* overheads are too high to be worthwhile otherwise.
|
|
*/
|
|
*tile_height = im->Xsize > 10000 ?
|
|
vips__thinstrip_height : vips__fatstrip_height;
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
/* We can't set n_lines for the current demand style: a later bit of
|
|
* the pipeline might see a different hint and we need to synchronise
|
|
* buffer sizes everywhere.
|
|
*
|
|
* We also can't depend on the current image size, since that might
|
|
* change down the pipeline too. Pick a typical image width.
|
|
*
|
|
* Pick the maximum buffer size we might possibly need, then round up
|
|
* to a multiple of tileheight.
|
|
*/
|
|
*n_lines = vips__tile_height *
|
|
VIPS_ROUND_UP( vips__tile_width * nthr,
|
|
typical_image_width ) /
|
|
typical_image_width;
|
|
*n_lines = VIPS_MAX( *n_lines, vips__fatstrip_height * nthr );
|
|
*n_lines = VIPS_MAX( *n_lines, vips__thinstrip_height * nthr );
|
|
*n_lines = VIPS_ROUND_UP( *n_lines, *tile_height );
|
|
|
|
/* We make this assumption in several places.
|
|
*/
|
|
g_assert( *n_lines % *tile_height == 0 );
|
|
|
|
VIPS_DEBUG_MSG( "vips_get_tile_size: %d by %d patches, "
|
|
"groups of %d scanlines\n",
|
|
*tile_width, *tile_height, *n_lines );
|
|
}
|
|
|
|
void
|
|
vips__thread_init( void )
|
|
{
|
|
static GPrivate private = { 0 };
|
|
|
|
is_vips_thread_key = &private;
|
|
|
|
if( vips__concurrency == 0 )
|
|
vips__concurrency = vips__concurrency_get_default();
|
|
}
|