문제는 백준의 빠른 A+B
보통 이런 문제는 original.cs 처럼 처리하는데 이건 시간 초과가 뜬다 콘솔 io가 졸라리 느리기 때문이다
일단 글에 나와 있는 추가 설명을 봐보자
제일 중요한 건 다음 문장같다: endl은 개행문자를 출력할 뿐만 아니라 출력 버퍼를 비우는 역할까지 합니다. 그래서 출력한 뒤 화면에 바로 보이게 할 수 있는데, 그 버퍼를 비우는 작업이 매우 느립니다.
그리고 다른 언어별 솔루션에서 파이썬의 경우는 sys.readline()을, java는 BufferedWriter을 c#은 StringBuilder 를 사용하라고 한다.
파이썬에서, 콘솔에 출력을 해도 버퍼가 플러시가 안되서 출력이 안되는 문제가 생긴적이 있던걸로 기억한다. 그래서 아마 파이썬은 출력 관련 무언가를 안해도 되는 거겟지 그렇다면 자바와 c#의 솔루션은 무엇을 의미할까??
일단 파이썬의 경우는 생각하지 말고 자바 솔루션과 c# 솔루션대로 해보고 되는지 해보자
만만한 놈부터 해보자 StringBuilder 는 상수값인 문자열을 변경하면 성능이 매우 떨어지니까 사용하는 클래스이다
이걸 사용하는 솔루션은 입력을 받고, 합을 계산한 다음 콘솔에 출력하고 -> 다음 입력을 받는게 아닌 콘솔에 출력할 결과값들을 StringBuilder에다가 모아뒀다가 한꺼번에 출력을 해서, 여기서 병목이 되는 Console.WriteLine을 마지막에 처리해줘 성능을 향상시키는 방식이다.
코드는 stringbuilder.cs
에 있다. original 코드랑 크게 다를건 없다. Console.WriteLine
을 result.AppendLine
으로 바꾼거 하나가 전부다. 백준 실행 결과를 보자.
맞았습니다!! 27948KB 1456ms
일단 시간초과는 나지 않고 통과한다. 그런데 메모리 사용량을 보면 모든 결과값이 메모리에 저장되어있다가 한꺼번에 내보내기 때문에 메모리를 좀 많이 먹는 걸 알 수 있다.
자바에서는 BufferedWriter 이고 비슷한게 c#에도 있나 찾아봤더니 나온게 요놈이다. 이름에서부터 알 수 있듯이 스트림에서 버퍼를 사용해서 성능을 향상시켜주는 놈인거 같은데 검색하면 다양한 글이랑 언제 써야하는지 등이 나온다. 간략한 예시로 쉽게 설명하는 좋은 글이 있다: 여기
암튼 요놈을 써서 콘솔 출력을 대채해보자.
코드는 buffered_writeonly.cs
맞았습니다!! 10088KB 1388ms
메모리 사용량이 확 준것 뿐만 아니라 속도도 약간 빨라졌다. 여기에 더불어서 콘솔 입력에도 버퍼를 적용해보자.
코드는 buffered.cs
맞았습니다!! 10084KB 1100ms
훨씬 빨라진 것 같다 여기에 비동기도 붙여보려 했는데? 왠지 모르게 이거는 런타임 에러가 꾸준히 난다 ㅠ 백준에서는 C# 6만 지원을 해서 7에 있는 async Main이 안되서 좀 문제있는거 같다
굳이 생소한 BufferedStream 을 안써도 될 것 같다
지금 가장 큰 병목은 standard output의 flush에서 생기는 병목이고 그러면 콘솔에서 AutoFlush를 안하고 WriteLine 하면 되지 않을까? 해서 오토플러시 관련해서 찾아보니 Console 자체에는 AutoFlush 등의 옵션이 없다. TextWriter 인 Console.Out 에도 없었고 대신 AutoFlush를 지원하는 StreamWriter를 쓰면 될 것 같더라
사실 방식도 BufferedStream과 거의 비슷한데 대신 StreamWriter 객체에서 AutoFlush 값만 false로 해두고 Console.WriteLine
대신 writer.WriteLine
을 사용하고 마지막에 writer.Close
로 버퍼를 클리어해주면 된다
코드는 autoflush.cs
맞았습니다!! 10316KB 1508ms
버퍼를 사용한 결과에 비해 성능은 조금 안좋지만 그래도 굳이 생소한 BufferedStream을 사용하지 않고 통과하는 것에 의의를 둘만하다
Console.Out 이 TextWriter 인데 AutoFlush를 지원 안한다? 이상해서 찾아보니 별 이상한 꼼수가 있더라
스택오버플로우에서 찾은 글인데, 대충 내용은 private으로 숨겨져있는 (Console.Out as SyncTextWriter)._out 이 StreamWriter 니까 이거의 AutoFlush를 끄면 된다고 한다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
대신 글이 좀 오래되서 그런지 SyncTextWriter가 TextWriter.SyncTextWriter가 아닌 System.IO.SyncTextWriter 이었는데 이 역시 정상적인 방법으로 접근할 수 없는건 동일 디버거로 적당히 찍어가면서 타입을 알아내고 코드에서 리플렉션으로 적용해보자
다음 리플렉션 코드로 콘솔에서 사용하는 StreamWriter을 가져올 수 있었다:
var writer = Console.Out.GetType().GetRuntimeFields().First().GetValue(Console.Out) as StreamWriter;
코드는 reflection.cs
맞았습니다!! 10400KB 1556ms
ㅋㅋ 암튼 제대로 돈다
결론.
- 이렇게 출력을 오질라게 많이 해야하는 경우엔 BufferedStream을 사용하면 성능 향상에 도움이 된다
- StringBuilder는 원래 많이 쓰는거라 익숙해지면 좋다
- StreamWriter 도 나쁘진 않은 해결방법이긴 한데 여기선 버퍼를 사용하는게 상황에 더 최적화되어있다
- 리플렉션은 잊자. 이렇게 코드 짜면 뺨맞는다