FE_Dev / lib /performance.ts
GitHub Actions
Deploy from GitHub Actions [dev] - 2025-10-31 07:28:50
68f7925
/**
* パフォーマンス最適化ユーティリティ
* HuggingFace Spaces制限に対応したメモリ管理とガベージコレクション
*/
// メモリ使用量の監視と報告
export function getMemoryUsage(): {
used: number;
total: number;
percentage: number;
} {
if (typeof process !== 'undefined' && process.memoryUsage) {
const usage = process.memoryUsage();
const total = usage.heapTotal;
const used = usage.heapUsed;
return {
used,
total,
percentage: (used / total) * 100,
};
}
return { used: 0, total: 0, percentage: 0 };
}
// メモリ閾値のチェック(デフォルト: 80%)
export function isMemoryThresholdExceeded(threshold = 0.8): boolean {
const { percentage } = getMemoryUsage();
return percentage > threshold * 100;
}
// 強制的なガベージコレクション(Node.js環境でのみ動作)
export function forceGarbageCollection(): void {
if (typeof global !== 'undefined' && global.gc) {
console.log('Forcing garbage collection...');
global.gc();
}
}
// 大きな画像データのクリーンアップ
export function cleanupImageData(imageData: unknown): void {
if (imageData && typeof imageData === 'object') {
// ImageDataやBufferの参照を解放
Object.keys(imageData).forEach((key) => {
delete (imageData as Record<string, unknown>)[key];
});
}
}
// Base64画像のサイズ最適化
export function optimizeBase64Image(
base64: string,
maxSizeKB: number = 1024,
): string {
// Base64のサイズを推定(約1.37倍のオーバーヘッド)
const estimatedSizeKB = (base64.length * 0.75) / 1024;
if (estimatedSizeKB > maxSizeKB) {
console.warn(
`Image size (${estimatedSizeKB.toFixed(2)}KB) exceeds limit (${maxSizeKB}KB)`,
);
// TODO: 実際の画像圧縮処理を実装
}
return base64;
}
// 処理の分割実行(大きなタスクを小さなチャンクに分割)
export async function processInChunks<T, R>(
items: T[],
processor: (item: T) => Promise<R>,
chunkSize: number = 10,
delayMs: number = 100,
): Promise<R[]> {
const results: R[] = [];
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
const chunkResults = await Promise.all(chunk.map(processor));
results.push(...chunkResults);
// メモリチェックとガベージコレクション
if (isMemoryThresholdExceeded(0.7)) {
forceGarbageCollection();
await new Promise((resolve) => setTimeout(resolve, delayMs * 2));
} else if (i + chunkSize < items.length) {
// 次のチャンクの前に短い遅延
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
return results;
}
// デバウンス処理(連続した呼び出しを制限)
export function debounce<T extends (...args: unknown[]) => unknown>(
func: T,
wait: number,
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null;
return function (...args: Parameters<T>) {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func(...args);
}, wait);
};
}
// スロットリング(一定時間内の呼び出し回数を制限)
export function throttle<T extends (...args: unknown[]) => unknown>(
func: T,
limit: number,
): (...args: Parameters<T>) => void {
let inThrottle = false;
return function (...args: Parameters<T>) {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// メモリ効率的な画像処理のためのキャッシュ管理
class ImageCache {
private cache: Map<string, { data: string; timestamp: number }> = new Map();
private maxSize: number;
private ttl: number;
constructor(maxSize: number = 10, ttlMinutes: number = 5) {
this.maxSize = maxSize;
this.ttl = ttlMinutes * 60 * 1000;
}
set(key: string, data: string): void {
// 古いエントリの削除
this.cleanup();
// キャッシュサイズ制限
if (this.cache.size >= this.maxSize) {
const oldestKey = Array.from(this.cache.entries()).sort(
(a, b) => a[1].timestamp - b[1].timestamp,
)[0][0];
this.cache.delete(oldestKey);
}
this.cache.set(key, { data, timestamp: Date.now() });
}
get(key: string): string | null {
const entry = this.cache.get(key);
if (!entry) return null;
// TTLチェック
if (Date.now() - entry.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
return entry.data;
}
cleanup(): void {
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > this.ttl) {
this.cache.delete(key);
}
}
}
clear(): void {
this.cache.clear();
}
}
export const imageCache = new ImageCache();
// 同時処理数の制限
export class ConcurrencyLimiter {
private running: number = 0;
private queue: Array<() => void> = [];
private maxConcurrent: number;
constructor(maxConcurrent: number = 3) {
this.maxConcurrent = maxConcurrent;
}
async execute<T>(task: () => Promise<T>): Promise<T> {
while (this.running >= this.maxConcurrent) {
await new Promise<void>((resolve) => {
this.queue.push(resolve);
});
}
this.running++;
try {
return await task();
} finally {
this.running--;
const next = this.queue.shift();
if (next) next();
}
}
}
// パフォーマンス監視
export class PerformanceMonitor {
private timings: Map<string, number> = new Map();
start(label: string): void {
this.timings.set(label, performance.now());
}
end(label: string): number {
const start = this.timings.get(label);
if (!start) {
console.warn(`No start time found for label: ${label}`);
return 0;
}
const duration = performance.now() - start;
this.timings.delete(label);
console.log(`[Performance] ${label}: ${duration.toFixed(2)}ms`);
return duration;
}
measure<T>(label: string, fn: () => T): T {
this.start(label);
try {
return fn();
} finally {
this.end(label);
}
}
async measureAsync<T>(label: string, fn: () => Promise<T>): Promise<T> {
this.start(label);
try {
return await fn();
} finally {
this.end(label);
}
}
}
export const performanceMonitor = new PerformanceMonitor();