다형성 유지와 성능 최적화 전략
인터페이스 기반의 로깅 방식은 다형성을 제공하여 다양한 구현체를 하나의 인터페이스로 통합할 수 있는 장점을 가지고 있습니다. 하지만 성능이 중요한 시스템에서는 호출 경로의 길이로 인해 성능 저하가 발생할 수 있습니다. 이러한 문제를 해결하기 위해 간접 호출 방식을 도입하면 성능을 향상시키면서도 다형성을 유지할 수 있습니다.
인터페이스 방식의 한계
인터페이스 방식은 다양한 로깅 프레임워크를 통합할 수 있지만, 호출 경로가 길어지면서 성능 오버헤드가 발생할 수 있습니다. 이는 성능이 중요한 시스템에서 문제로 작용할 수 있습니다.
간접 호출 방식으로 성능 개선
간접 호출 방식은 메서드를 인터페이스로 구현하는 대신, 대리자를 사용하여 메서드를 직접 호출하는 방식을 활용합니다. 이 방식은 호출 경로를 단축시켜 성능을 개선하면서도 다형성을 유지할 수 있습니다.
간접 호출 방식 예시
public class LoggerManager
{
private Dictionary<LogType, Action<string>> _loggers = new Dictionary<LogType, Action<string>>();
public LogType CurrentLogType { get; set; }
public LoggerManager(LogType logType)
{
CurrentLogType = logType;
}
public void RegisterLogger(LogType logType, Action<string> logAction)
{
_loggers[logType] = logAction;
}
public void LogInformation(string message)
{
if (_loggers.TryGetValue(CurrentLogType, out var logAction))
{
logAction(message);
}
else
{
Console.WriteLine($"Logger for '{CurrentLogType}' not found.");
}
}
}
성능 테스트 및 결과
테스트 방법
- 동일한 조건 하에 500,000개의 로그 메시지를 기록
- 5회 반복하여 평균 시간을 산출
- 성능 측정을 위해 .NET의
Stopwatch
클래스 사용
테스트 결과
로깅 방식 | 평균 시간(ms) |
---|---|
Serilog 직접 호출 | 2216.6 |
Serilog 비동기 호출 | 2342.2 |
Serilog 인터페이스 호출 | 2605.8 |
Serilog 간접 호출 | 2368.8 |
Zerolog 직접 호출 | 257.2 |
Zerolog 인터페이스 호출 | 316 |
Zerolog 간접 호출 | 302.6 |
테스트 해석
테스트 결과, 간접 호출 방식이 인터페이스 방식보다 더 나은 성능을 보여주었습니다. 이는 대리자를 사용하여 호출 경로를 단축시킨 결과입니다.
테스트 코드
var path1 = $@"/logs/zerolog.log";
var loggerManager = new LoggerManager();
if (File.Exists(path1))
File.Delete(path1);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
// Zerolog 로거 등록
var zerologLogger = new ZerologLogger(path1);
loggerManager.RegisterLogger("zerolog", zerologLogger.Information);
// 성능 테스트를 위한 로깅
var elapsedList = new List<long>();
for (int c = 0; c < 5; c++)
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < 500_000; i++)
{
loggerManager.LogInformation("zerolog", $"Log Performance Test. No : {c}.{i}. elapsed : {sw.ElapsedMilliseconds}");
}
LogManager.Flush();
elapsedList.Add(sw.ElapsedMilliseconds);
}
// Flush 및 종료
LogManager.Flush();
LogManager.Shutdown();
Console.WriteLine($"Elapsed Time : {elapsedList.Average()}");
// Elapsed Time : 343.6
public class ZerologLogger
{
private ZeroLog.Log _logger;
public ZerologLogger(string path)
{
LogManager.Initialize(new ZeroLogConfiguration
{
LogMessagePoolSize = 10000000,
RootLogger =
{
Appenders =
{
new DateAndSizeRollingFileAppender(path)
}
}
});
_logger = LogManager.GetLogger("ZeroLogger");
}
public void Information(string message)
{
_logger?.Info(message);
}
}
public class LoggerManager
{
private Dictionary<string, Action<string>> _loggers = new Dictionary<string, Action<string>>();
public void RegisterLogger(string key, Action<string> logAction)
{
_loggers[key] = logAction;
}
public void LogInformation(string key, string message)
{
if (_loggers.TryGetValue(key, out var logAction))
{
logAction(message);
}
else
{
Console.WriteLine($"Logger with key '{key}' not found.");
}
}
}
간접 호출 방식의 장점
성능 최적화
간접 호출 방식은 메서드 대리자를 통해 인터페이스 방식에서 발생하는 런타임 오버헤드를 줄일 수 있습니다. 이는 성능이 중요한 애플리케이션에서 큰 이점을 제공합니다.
유지보수성 강화
간접 호출 방식은 코드 구조를 단순화하고, 유지보수를 용이하게 합니다. 이를 통해 시스템 확장과 변화에 유연하게 대처할 수 있습니다.
확장성 향상
새로운 로깅 시스템을 도입하거나 기존 시스템을 확장할 때, 간접 호출 방식은 기존 코드의 변경을 최소화할 수 있습니다. 새로운 로거를 추가하거나 대체하는 작업이 간편해집니다.
코드 간결화
간접 호출 방식은 코드의 복잡한 의존성을 줄여주고, 로깅 시스템 간의 결합도를 낮춰 코드의 재사용성과 유지보수성을 향상시킵니다.
로깅 방식 변경 용이성
간접 호출 방식에서는 LogType
만 변경하면 다른 로깅 방식으로 쉽게 전환할 수 있어 유지보수 시 로깅 시스템 교체가 간단해집니다.
결합도 감소
로깅 구현체와 사용 코드 간의 결합도를 낮춰, 시스템 변경에 유연하게 대응할 수 있습니다.
테스트 용이성
간접 호출 방식은 대리자를 모킹하거나 테스트 환경에 맞춰 유연하게 동작을 변경할 수 있어, 테스트가 간편하고 효율적입니다.
결론
간접 호출 방식은 성능 최적화와 다형성 유지라는 두 가지 목표를 모두 충족할 수 있는 효과적인 방법입니다. 성능이 중요한 애플리케이션에서 인터페이스 방식의 성능 오버헤드를 줄이고, 메서드 호출을 최적화함으로써 더 빠른 응답성을 제공할 수 있습니다. 이를 통해 성능이 중요한 시스템에서 더욱 효율적인 로깅 전략을 구현할 수 있습니다.