-
Notifications
You must be signed in to change notification settings - Fork 0
Perf : LOH 최적화 #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Perf : LOH 최적화 #21
Changes from all commits
e032d5e
ace9e1a
d4f8631
e50c079
81dae08
ca62d4d
42eb294
0eaa3d8
369297e
044083c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -50,8 +50,10 @@ public async Task<TextToSpeechResponse> TextToSpeechAsync(TextToSpeechRequest re | |||||||||||||||||||||||||
return voiceResponse; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// 스트림 기반으로 음성 데이터 읽기 (LOH 방지) | ||||||||||||||||||||||||||
voiceResponse.AudioData = await ReadAudioDataWithPoolAsync(response.Content); | ||||||||||||||||||||||||||
// ArrayPool 기반으로 음성 데이터 읽기 (LOH 방지) | ||||||||||||||||||||||||||
var (memoryOwner, dataSize) = await ReadAudioDataWithPoolAsync(response.Content); | ||||||||||||||||||||||||||
voiceResponse.AudioMemoryOwner = memoryOwner; | ||||||||||||||||||||||||||
voiceResponse.AudioDataSize = dataSize; | ||||||||||||||||||||||||||
voiceResponse.ContentType = response.Content.Headers.ContentType?.ToString(); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if (response.Headers.Contains("X-Audio-Length")) | ||||||||||||||||||||||||||
|
@@ -64,7 +66,7 @@ public async Task<TextToSpeechResponse> TextToSpeechAsync(TextToSpeechRequest re | |||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
_logger.LogDebug("[TTS][Response] 오디오 길이: {AudioLength:F2}초, ContentType: {ContentType}, 바이트: {Length}, 소요시간: {Elapsed}ms", | ||||||||||||||||||||||||||
voiceResponse.AudioLength, voiceResponse.ContentType, voiceResponse.AudioData?.Length ?? 0, elapsed); | ||||||||||||||||||||||||||
voiceResponse.AudioLength, voiceResponse.ContentType, voiceResponse.AudioDataSize, elapsed); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return voiceResponse; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
@@ -82,44 +84,63 @@ public async Task<TextToSpeechResponse> TextToSpeechAsync(TextToSpeechRequest re | |||||||||||||||||||||||||
/// <summary> | ||||||||||||||||||||||||||
/// ArrayPool을 사용하여 스트림 기반으로 음성 데이터를 읽습니다 (LOH 할당 방지) | ||||||||||||||||||||||||||
/// </summary> | ||||||||||||||||||||||||||
private async Task<byte[]?> ReadAudioDataWithPoolAsync(HttpContent content) | ||||||||||||||||||||||||||
private async Task<(IMemoryOwner<byte>?, int)> ReadAudioDataWithPoolAsync(HttpContent content) | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
const int chunkSize = 32768; // 32KB 청크 크기 | ||||||||||||||||||||||||||
byte[]? buffer = null; | ||||||||||||||||||||||||||
MemoryStream? memoryStream = null; | ||||||||||||||||||||||||||
byte[]? readBuffer = null; | ||||||||||||||||||||||||||
IMemoryOwner<byte>? owner = null; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
try | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
buffer = _arrayPool.Rent(chunkSize); | ||||||||||||||||||||||||||
memoryStream = new MemoryStream(); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
readBuffer = _arrayPool.Rent(chunkSize); | ||||||||||||||||||||||||||
using var stream = await content.ReadAsStreamAsync(); | ||||||||||||||||||||||||||
int bytesRead; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// 청크 단위로 데이터 읽어서 MemoryStream에 복사 | ||||||||||||||||||||||||||
while ((bytesRead = await stream.ReadAsync(buffer, 0, chunkSize)) > 0) | ||||||||||||||||||||||||||
// 초기 버퍼 렌트(증분 확장 전략) | ||||||||||||||||||||||||||
owner = MemoryPool<byte>.Shared.Rent(chunkSize); | ||||||||||||||||||||||||||
int total = 0; | ||||||||||||||||||||||||||
while (true) | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
// 여유 공간 없으면 확장 | ||||||||||||||||||||||||||
if (total == owner.Memory.Length) | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
var newOwner = MemoryPool<byte>.Shared.Rent(Math.Min(owner.Memory.Length * 2, int.MaxValue)); | ||||||||||||||||||||||||||
owner.Memory.Span.Slice(0, total).CopyTo(newOwner.Memory.Span); | ||||||||||||||||||||||||||
owner.Dispose(); | ||||||||||||||||||||||||||
owner = newOwner; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
Comment on lines
+106
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 버퍼 확장 시 int 오버플로 가능성 — 안전한 배수 계산 필요 owner.Memory.Length*2에서 오버플로가 발생하면 음수 크기로 Rent 호출될 수 있습니다. - var newOwner = MemoryPool<byte>.Shared.Rent(Math.Min(owner.Memory.Length * 2, int.MaxValue));
+ int cur = owner.Memory.Length;
+ int next = (cur > (int.MaxValue / 2)) ? int.MaxValue : cur * 2;
+ var newOwner = MemoryPool<byte>.Shared.Rent(next); 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
int toRead = Math.Min(chunkSize, owner.Memory.Length - total); | ||||||||||||||||||||||||||
int bytesRead = await stream.ReadAsync(readBuffer, 0, toRead); | ||||||||||||||||||||||||||
if (bytesRead == 0) break; | ||||||||||||||||||||||||||
readBuffer.AsSpan(0, bytesRead).CopyTo(owner.Memory.Span.Slice(total)); | ||||||||||||||||||||||||||
total += bytesRead; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if (total == 0) | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
await memoryStream.WriteAsync(buffer, 0, bytesRead); | ||||||||||||||||||||||||||
owner.Dispose(); | ||||||||||||||||||||||||||
_logger.LogDebug("[TTS][ArrayPool] 비어있는 오디오 스트림"); | ||||||||||||||||||||||||||
return (null, 0); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
var result = memoryStream.ToArray(); | ||||||||||||||||||||||||||
_logger.LogDebug("[TTS][ArrayPool] 음성 데이터 읽기 완료: {Size} bytes, 청크 크기: {ChunkSize}", | ||||||||||||||||||||||||||
result.Length, chunkSize); | ||||||||||||||||||||||||||
total, chunkSize); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return result; | ||||||||||||||||||||||||||
return (owner, total); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
catch (Exception ex) | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
_logger.LogError(ex, "[TTS][ArrayPool] 음성 데이터 읽기 실패"); | ||||||||||||||||||||||||||
return null; | ||||||||||||||||||||||||||
owner?.Dispose(); | ||||||||||||||||||||||||||
return (null, 0); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
finally | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
if (buffer != null) | ||||||||||||||||||||||||||
if (readBuffer != null) | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
_arrayPool.Return(buffer); | ||||||||||||||||||||||||||
_arrayPool.Return(readBuffer); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
memoryStream?.Dispose(); | ||||||||||||||||||||||||||
// owner는 정상 경로에서 호출자에게 반환됨. 예외 시 위에서 Dispose 처리. | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
IMemoryOwner/크기 세터 접근 축소로 오·남용 방지
소비자가 임의로 교체하면 이중 Dispose/누수 가능성이 있습니다. 동일 어셈블리에서만 설정되도록 setter를 internal로 축소하세요.
📝 Committable suggestion
🤖 Prompt for AI Agents