perf: 并发worker数可配置(默认4)与MIME检测并行化
This commit is contained in:
@@ -9,3 +9,5 @@ database.dsn=media-info.db
|
|||||||
ffmpeg.path=
|
ffmpeg.path=
|
||||||
# 整理时是否自动转正带 EXIF 旋转标记的图片(true=是,默认;false=只按显示方向归类不转正)
|
# 整理时是否自动转正带 EXIF 旋转标记的图片(true=是,默认;false=只按显示方向归类不转正)
|
||||||
organize.rotateImages=true
|
organize.rotateImages=true
|
||||||
|
# 探测/认类型/移动阶段的并发 worker 数(默认 4;0 表示用满全部 CPU 核数)
|
||||||
|
organize.workers=4
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/magiconair/properties"
|
"github.com/magiconair/properties"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,12 +18,14 @@ import (
|
|||||||
// - AutoOpen:启动后要不要自动打开浏览器
|
// - AutoOpen:启动后要不要自动打开浏览器
|
||||||
// - FFmpegPath:ffmpeg 程序路径(用于把带 EXIF 旋转标记的图片转正);留空自动在 PATH 里找
|
// - FFmpegPath:ffmpeg 程序路径(用于把带 EXIF 旋转标记的图片转正);留空自动在 PATH 里找
|
||||||
// - RotateImages:整理时是否自动转正带 EXIF 旋转标记的图片(默认 true)
|
// - RotateImages:整理时是否自动转正带 EXIF 旋转标记的图片(默认 true)
|
||||||
|
// - Workers:探测/MIME/移动阶段的并发 worker 数(默认 4;设 0 表示用满全部 CPU 核数)
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Port int
|
Port int
|
||||||
Dsn string
|
Dsn string
|
||||||
AutoOpen bool
|
AutoOpen bool
|
||||||
FFmpegPath string
|
FFmpegPath string
|
||||||
RotateImages bool
|
RotateImages bool
|
||||||
|
Workers int
|
||||||
}
|
}
|
||||||
|
|
||||||
// appConfig 当前生效的配置(程序运行期间一直不变)。
|
// appConfig 当前生效的配置(程序运行期间一直不变)。
|
||||||
@@ -33,7 +39,7 @@ func GetAppConfig() Config {
|
|||||||
// LoadAppConfig 从 application.properties 读配置;文件不存在就用默认值。
|
// LoadAppConfig 从 application.properties 读配置;文件不存在就用默认值。
|
||||||
func LoadAppConfig() {
|
func LoadAppConfig() {
|
||||||
// 先给一套默认值
|
// 先给一套默认值
|
||||||
appConfig = Config{Port: 8080, Dsn: "media-info.db", AutoOpen: true, RotateImages: true}
|
appConfig = Config{Port: 8080, Dsn: "media-info.db", AutoOpen: true, RotateImages: true, Workers: 4}
|
||||||
|
|
||||||
p, err := properties.LoadFile("application.properties", properties.UTF8)
|
p, err := properties.LoadFile("application.properties", properties.UTF8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -52,4 +58,24 @@ func LoadAppConfig() {
|
|||||||
appConfig.FFmpegPath = v
|
appConfig.FFmpegPath = v
|
||||||
}
|
}
|
||||||
appConfig.RotateImages = p.GetBool("organize.rotateImages", true)
|
appConfig.RotateImages = p.GetBool("organize.rotateImages", true)
|
||||||
|
// 并发 worker 数:只认非负整数,填了别的就保持默认
|
||||||
|
if v, ok := p.Get("organize.workers"); ok && strings.TrimSpace(v) != "" {
|
||||||
|
if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil && n >= 0 {
|
||||||
|
appConfig.Workers = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkerCount 返回探测/MIME/移动阶段使用的并发 worker 数:
|
||||||
|
// - 配置里 organize.workers > 0 时用配置值(默认 4);
|
||||||
|
// - 配置为 0 时用满全部 CPU 核数。
|
||||||
|
func WorkerCount() int {
|
||||||
|
if appConfig.Workers > 0 {
|
||||||
|
return appConfig.Workers
|
||||||
|
}
|
||||||
|
n := runtime.NumCPU()
|
||||||
|
if n < 1 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"kenaito-media-picker/internal/config"
|
||||||
|
|
||||||
uuid "github.com/satori/go.uuid"
|
uuid "github.com/satori/go.uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,15 +31,10 @@ type MoveJob struct {
|
|||||||
// Mover 文件移动器。目前不需要记录任何状态,所以是个空壳结构体。
|
// Mover 文件移动器。目前不需要记录任何状态,所以是个空壳结构体。
|
||||||
type Mover struct{}
|
type Mover struct{}
|
||||||
|
|
||||||
// workerCount 并行搬文件的工人数:直接用满 CPU 核心数。
|
// workerCount 并行搬文件的工人数:默认 4 个(配置 organize.workers 可调,0 表示用满 CPU)。
|
||||||
// 你的机器够强、平时又没别的活,那就让它全速跑,这就是空闲时的极限值。
|
// 搬文件时磁盘 IO 才是瓶颈,并发太大反而把 CPU/磁盘都拖垮,给个配置让用户自行权衡。
|
||||||
// (如果搬文件的同时机器还在干别的活、被拖慢了,再手动把这个数调低即可。)
|
|
||||||
func workerCount() int {
|
func workerCount() int {
|
||||||
n := runtime.NumCPU()
|
return config.WorkerCount()
|
||||||
if n < 1 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MoveAll 把一批"搬文件任务"全局并行搬完——不管任务分散在几个目录,
|
// MoveAll 把一批"搬文件任务"全局并行搬完——不管任务分散在几个目录,
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"kenaito-media-picker/internal/classify"
|
"kenaito-media-picker/internal/classify"
|
||||||
|
"kenaito-media-picker/internal/config"
|
||||||
"kenaito-media-picker/internal/digest"
|
"kenaito-media-picker/internal/digest"
|
||||||
"kenaito-media-picker/internal/mover"
|
"kenaito-media-picker/internal/mover"
|
||||||
"kenaito-media-picker/internal/probe"
|
"kenaito-media-picker/internal/probe"
|
||||||
@@ -93,14 +93,10 @@ func (o *Organizer) report(phase string, done, total int) {
|
|||||||
|
|
||||||
// ============================ 并行小工具 ============================
|
// ============================ 并行小工具 ============================
|
||||||
|
|
||||||
// workerCount 并行探测的工人数:直接用满 CPU 核心数(空闲时的极限值)。
|
// workerCount 并行探测的工人数:默认 4 个(配置 organize.workers 可调,0 表示用满 CPU)。
|
||||||
// 多核机器并行探测能明显提速;如果机器同时还在跑别的重活,再手动调低即可。
|
// 多核机器并行探测能明显提速;但并发太大会吃满 CPU,给个配置让用户自行权衡。
|
||||||
func workerCount() int {
|
func workerCount() int {
|
||||||
n := runtime.NumCPU()
|
return config.WorkerCount()
|
||||||
if n < 1 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// runParallel 用固定数量的工人并行处理一批东西,结果按原来的顺序放回。
|
// runParallel 用固定数量的工人并行处理一批东西,结果按原来的顺序放回。
|
||||||
|
|||||||
@@ -11,8 +11,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"kenaito-media-picker/internal/config"
|
||||||
"kenaito-media-picker/internal/probe"
|
"kenaito-media-picker/internal/probe"
|
||||||
|
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
@@ -106,12 +109,35 @@ func Scan(opts Options) (*Result, error) {
|
|||||||
if opts.OnProgress != nil {
|
if opts.OnProgress != nil {
|
||||||
opts.OnProgress(total, total)
|
opts.OnProgress(total, total)
|
||||||
}
|
}
|
||||||
for i := range res.Files {
|
// 并行认类型:每个 worker 只写自己名下那个元素(res.Files[i]),并发安全;
|
||||||
|
// 认类型要读文件内容,数量多时串行太慢,并行能明显提速。
|
||||||
|
workers := config.WorkerCount()
|
||||||
|
if workers > total {
|
||||||
|
workers = total
|
||||||
|
}
|
||||||
|
if workers < 1 {
|
||||||
|
workers = 1
|
||||||
|
}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var done int32
|
||||||
|
jobs := make(chan int)
|
||||||
|
for w := 0; w < workers; w++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := range jobs {
|
||||||
res.Files[i].Mime = detectMime(res.Files[i].Path)
|
res.Files[i].Mime = detectMime(res.Files[i].Path)
|
||||||
if opts.OnProgress != nil {
|
if opts.OnProgress != nil {
|
||||||
opts.OnProgress(i+1, total)
|
opts.OnProgress(int(atomic.AddInt32(&done, 1)), total)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
for i := range res.Files {
|
||||||
|
jobs <- i
|
||||||
|
}
|
||||||
|
close(jobs)
|
||||||
|
wg.Wait()
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user