Default max_concurrent_processing to half the CPUs pixa sees (closes #242)
The default was one image per CPU Go uses, and a container with no CPU limit sees every CPU of the host. It is now half of runtime.GOMAXPROCS(0), rounded down and at least 1, from imageprocessor.DefaultMaxConcurrentProcessing, which both the config and imageprocessor.New use. The two existing tests of the default expect the new one, as the owner allowed on the issue. "Running under upaas" gives the worst-case memory, with a per-image peak of about 1.6 GiB measured on pixad for an 8192x8192 source in each output format, and tells the operator to set PIXA_MAX_CONCURRENT_PROCESSING and PIXA_UPSTREAM_CONNECTIONS to fit the container's memory. Model: opus-5-5
This commit is contained in:
@@ -20,11 +20,11 @@ import (
|
||||
var vipsOnce sync.Once
|
||||
|
||||
// initVips initializes libvips with quiet logging, one worker thread per
|
||||
// image and no operation cache. Process already works on one image per CPU
|
||||
// by default, so more threads per image would only compete for the CPUs.
|
||||
// Each request decodes different source bytes, so the operation cache
|
||||
// would rarely be hit and would hold memory outside MaxConcurrentProcessing;
|
||||
// repeated requests are served from pixa's disk cache instead.
|
||||
// image and no operation cache. With one thread each, the images processed
|
||||
// at once use no more CPUs than MaxConcurrentProcessing. Each request
|
||||
// decodes different source bytes, so the operation cache would rarely be
|
||||
// hit and would hold memory outside MaxConcurrentProcessing; repeated
|
||||
// requests are served from pixa's disk cache instead.
|
||||
func initVips() {
|
||||
vipsOnce.Do(func() {
|
||||
vips.LoggingSettings(nil, vips.LogLevelError)
|
||||
@@ -179,10 +179,19 @@ type Params struct {
|
||||
// If <= 0, DefaultMaxInputBytes is used.
|
||||
MaxInputBytes int64
|
||||
// MaxConcurrentProcessing is the most images processed at once.
|
||||
// If <= 0, the number of CPUs Go uses (runtime.GOMAXPROCS(0)) is used.
|
||||
// If <= 0, DefaultMaxConcurrentProcessing() is used.
|
||||
MaxConcurrentProcessing int
|
||||
}
|
||||
|
||||
// DefaultMaxConcurrentProcessing returns the most images processed at once
|
||||
// when none is set: half the CPUs Go uses (runtime.GOMAXPROCS(0), which
|
||||
// follows a container's CPU limit), rounded down, and at least 1.
|
||||
func DefaultMaxConcurrentProcessing() int {
|
||||
const cpusPerImage = 2
|
||||
|
||||
return max(runtime.GOMAXPROCS(0)/cpusPerImage, 1)
|
||||
}
|
||||
|
||||
// New creates a new image processor with the given parameters.
|
||||
// A zero-value Params{} uses sensible defaults.
|
||||
func New(params Params) *ImageProcessor {
|
||||
@@ -195,7 +204,7 @@ func New(params Params) *ImageProcessor {
|
||||
|
||||
maxConcurrentProcessing := params.MaxConcurrentProcessing
|
||||
if maxConcurrentProcessing <= 0 {
|
||||
maxConcurrentProcessing = runtime.GOMAXPROCS(0)
|
||||
maxConcurrentProcessing = DefaultMaxConcurrentProcessing()
|
||||
}
|
||||
|
||||
return &ImageProcessor{
|
||||
|
||||
Reference in New Issue
Block a user