Skip to content

Instantly share code, notes, and snippets.

@20chan
Created June 10, 2019 06:31
Show Gist options
  • Save 20chan/67fff1d3da8549d8540239c39f3f8bcd to your computer and use it in GitHub Desktop.
Save 20chan/67fff1d3da8549d8540239c39f3f8bcd to your computer and use it in GitHub Desktop.

빠른 A+B in C#

문제는 백준의 빠른 A+B

보통 이런 문제는 original.cs 처럼 처리하는데 이건 시간 초과가 뜬다 콘솔 io가 졸라리 느리기 때문이다

일단 글에 나와 있는 추가 설명을 봐보자

제일 중요한 건 다음 문장같다: endl은 개행문자를 출력할 뿐만 아니라 출력 버퍼를 비우는 역할까지 합니다. 그래서 출력한 뒤 화면에 바로 보이게 할 수 있는데, 그 버퍼를 비우는 작업이 매우 느립니다. 그리고 다른 언어별 솔루션에서 파이썬의 경우는 sys.readline()을, java는 BufferedWriter을 c#은 StringBuilder 를 사용하라고 한다.

파이썬에서, 콘솔에 출력을 해도 버퍼가 플러시가 안되서 출력이 안되는 문제가 생긴적이 있던걸로 기억한다. 그래서 아마 파이썬은 출력 관련 무언가를 안해도 되는 거겟지 그렇다면 자바와 c#의 솔루션은 무엇을 의미할까??

일단 파이썬의 경우는 생각하지 말고 자바 솔루션과 c# 솔루션대로 해보고 되는지 해보자

StringBuilder

만만한 놈부터 해보자 StringBuilder 는 상수값인 문자열을 변경하면 성능이 매우 떨어지니까 사용하는 클래스이다

이걸 사용하는 솔루션은 입력을 받고, 합을 계산한 다음 콘솔에 출력하고 -> 다음 입력을 받는게 아닌 콘솔에 출력할 결과값들을 StringBuilder에다가 모아뒀다가 한꺼번에 출력을 해서, 여기서 병목이 되는 Console.WriteLine을 마지막에 처리해줘 성능을 향상시키는 방식이다.

코드는 stringbuilder.cs 에 있다. original 코드랑 크게 다를건 없다. Console.WriteLineresult.AppendLine 으로 바꾼거 하나가 전부다. 백준 실행 결과를 보자.

맞았습니다!!	27948KB	1456ms

일단 시간초과는 나지 않고 통과한다. 그런데 메모리 사용량을 보면 모든 결과값이 메모리에 저장되어있다가 한꺼번에 내보내기 때문에 메모리를 좀 많이 먹는 걸 알 수 있다.

BufferedStream

자바에서는 BufferedWriter 이고 비슷한게 c#에도 있나 찾아봤더니 나온게 요놈이다. 이름에서부터 알 수 있듯이 스트림에서 버퍼를 사용해서 성능을 향상시켜주는 놈인거 같은데 검색하면 다양한 글이랑 언제 써야하는지 등이 나온다. 간략한 예시로 쉽게 설명하는 좋은 글이 있다: 여기

암튼 요놈을 써서 콘솔 출력을 대채해보자. 코드는 buffered_writeonly.cs

맞았습니다!!	10088KB	1388ms

메모리 사용량이 확 준것 뿐만 아니라 속도도 약간 빨라졌다. 여기에 더불어서 콘솔 입력에도 버퍼를 적용해보자. 코드는 buffered.cs

맞았습니다!!	10084KB	1100ms

훨씬 빨라진 것 같다 여기에 비동기도 붙여보려 했는데? 왠지 모르게 이거는 런타임 에러가 꾸준히 난다 ㅠ 백준에서는 C# 6만 지원을 해서 7에 있는 async Main이 안되서 좀 문제있는거 같다

StreamWriter

굳이 생소한 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 도 나쁘진 않은 해결방법이긴 한데 여기선 버퍼를 사용하는게 상황에 더 최적화되어있다
  • 리플렉션은 잊자. 이렇게 코드 짜면 뺨맞는다
using System;
using System.IO;
using System.Linq;
namespace FastStdIO {
class Program {
static void Main(string[] args) {
int n = int.Parse(Console.ReadLine());
var writer = new StreamWriter(Console.OpenStandardOutput());
writer.AutoFlush = false;
for (int i = 0; i < n; i++) {
var line = Console.ReadLine();
var sum = line.Split().Select(int.Parse).Sum();
writer.WriteLine(sum.ToString());
}
writer.Close();
}
}
}
using System;
using System.IO;
using System.Linq;
namespace FastStdIO {
class Program {
static void Main(string[] args) {
using (var inStream = new BufferedStream(Console.OpenStandardInput()))
using (var outStream = new BufferedStream(Console.OpenStandardOutput()))
using (var reader = new StreamReader(inStream))
using (var writer = new StreamWriter(outStream)) {
int n = int.Parse(reader.ReadLine());
for (int i = 0; i < n; i++) {
var sum = reader.ReadLine().Split().Select(int.Parse).Sum();
writer.WriteLine(sum);
}
}
}
}
}
using System;
using System.IO;
using System.Linq;
namespace FastStdIO {
class Program {
static void Main(string[] args) {
using (var outStream = new BufferedStream(Console.OpenStandardOutput()))
using (var writer = new StreamWriter(outStream)) {
int n = int.Parse(Console.ReadLine());
for (int i = 0; i < n; i++) {
var sum = Console.ReadLine().Split().Select(int.Parse).Sum();
writer.WriteLine(sum);
}
}
}
}
}
using System;
using System.IO;
using System.Linq;
namespace FastStdIO {
class Program {
static void Main(string[] args) {
var n = int.Parse(Console.ReadLine());
for (int i = 0; i < n; i++) {
var sum = Console.ReadLine().Split().Select(int.Parse).Sum();
Console.WriteLine(sum);
}
}
}
}
using System;
using System.IO;
using System.Linq;
using System.Reflection;
namespace FastStdIO {
class Program {
static void Main(string[] args) {
var writer = Console.Out.GetType().GetRuntimeFields().First().GetValue(Console.Out) as StreamWriter;
writer.AutoFlush = false;
int n = int.Parse(Console.ReadLine());
for (int i = 0; i < n; i++) {
var line = Console.ReadLine();
var sum = line.Split().Select(int.Parse).Sum();
Console.WriteLine(sum.ToString());
}
writer.Close();
}
}
}
using System;
using System.Linq;
using System.Text;
namespace FastStdIO {
class Program {
static void Main(string[] args) {
int n = int.Parse(Console.ReadLine());
var result = new StringBuilder();
for (int i = 0; i < n; i++) {
var sum = Console.ReadLine().Split().Select(int.Parse).Sum();
result.AppendLine(sum.ToString());
}
Console.Write(result);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment