ݺߣ

ݺߣShare a Scribd company logo
200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활
NAVER WEBTOON | 장수한
성능을고민하는슬기로운개발자생활
케이스 스터디 – 일상적인 코드들
• 다음과 같은 기능을 수행하는 코드가 필요합니다.
• 사용자들이 작성한 메모를 로컬 DB에서 가져온 뒤 뷰모델로 변환하고,
• 유효하지 않은 데이터를 삭제하고 식별자가 같으면 가장 최근의 메모를 표시한다.
• 변환된 메모 데이터는 생성날짜를 기준으로 내림차순 정렬되어 사용자에게 제공된다.
• 다음과 같은 기능 구현이 필요합니다.
• 로컬 DB로부터 가져온 N개의 데이터를 모델로 변환하여,
• 데이터 유효성 검사를 수행하여 유효하지 않은 모델을 제거하고, 모델 전체에 대한 중복제거를 진행한 뒤
• 시간을 기준으로 내림차순 정렬하여 반환한다.
• 로컬 DB에 저장된 데이터는 다음과 같이 구성되어 있습니다.
• [{id: Int?, content: String?, date: Date?}]
• 데이터 유효성 검사는 다음과 같이 진행합니다.
• 3개의 필드 중 하나라도 값이 없으면 유효하지 않은 데이터로 판단한다.
• 데이터 중복제거는 다음과 같이 진행합니다.
• ID는 유일해야 한다. ID가 중복되는 경우 시간 정보가 큰 값(최근값)을 사용한다.
케이스 스터디 – 일상적인 코드들
• 일단 모델을 구현해보겠습니다.
케이스 스터디 – 일상적인 코드들
• 앞의 코드를 활용하여 직독직해 수준의 구현을 해보겠습니다.
케이스 스터디 – 일상적인 코드들
• 코드를 다듬어볼께요.
N의 습격 – 비일상적인 환경들
• 변화된 코드를 보면?
• for-in 대신 map, filter를 사용하여 반복문을 깔끔하게 정리했습니다.
• 각각의 기능이 map, filter에 함수로 할당되어 코드가 대폭 줄어들었습니다.
• 결론. 겉으로 보기에 코드가 깔끔해졌고, 트렌디하며, 한마디로 뭔가 있어보입니다.
• 무엇이 문제인가?
• 앞의 코드는 개발자가 반드시 고려해야하는 부분을 처음부터 고려하지 않은 코드입니다.
• 이를 무시하고, 코드의 모양을 아름답게 개선하여 문제는 더 깊게 숨겨졌습니다.
• 개발자가 반드시 고려해야할 사항
• 구현된 로직이 동작할 환경은 어떤가
• 적절하게 자료구조가 사용되었는가
• 작성된 로직의 복잡도는 어떻게 되는가
• 해당 코드가 포함되어 배포되었고, 메모를 10000개 가지고 있는 사용자와 만났습니다.
• …그리고 앱 실행 후, 메모를 보기 위해 사용자는 30초 이상의 시간을 기다려야 합니다.
이것을 고려하지 않으면,
개발자는 재앙을 맞이하게 됩니다.
N의 습격 – 비일상적인 환경들
• 정말 비일상적인 환경일까요?
• 메모를 10000개나 가지고 있는 사용자가 있긴한가요?
• 그리고, 10000개나 가지고 있으니까 당연히 느린거 아닌가요?
• …정말 그럴까요?
• 하나씩 살펴보겠습니다.
• 컴파일 오류가 없었고, 테스트 데이터를 넣었을 때 원하는 결과도 나왔죠.
• UI와 기능을 연결해서, 데이터가 잘 표시되는지도 확인했을 겁니다.
• 하지만 이건 환경이 아닙니다. 그저, 로직이 동작하는지 확인한거죠.
• 개발자가 반드시 고려해야할 사항, 챙기셨나요?
• 구현된 로직이 동작할 환경은 어떤가
• 적절하게 자료구조가 사용되었는가
• 작성된 로직의 복잡도는 어떻게 되는가
범인은 개발자 – 최적화된 코드들
• 구현된 로직이 동작할 환경은 어떤가
• 로직 구현 후, 테스트 데이터를 100개 정도 만들었습니다.
• 속도 측정을 해보니, 0.004초(시뮬레이터 기준). 속도 문제가 없네요.
• 그런데 왜 테스트 데이터 100개죠?
• 정말 사용자들은 그 정도의 데이터만 가지고 있을까요?
• 조금 더 신경을 써서, 1000개의 메모를 가지고 있는 사용자를 고려해봅니다.
• 속도 측정을 해보니, 0.4초(시뮬레이터 기준). 속도 문제가 없네요.
• 정말 속도 문제가 없을까요?
• 데이터는 10배 증가했는데, 왜 속도는 100배나 증가했을까요?
• 그리고 무엇보다, 사용자 데이터는 추정하면 안 됩니다.
• 여건이 허락하는 선에서, 사용자의 환경이 어떤지 알고 있어야 합니다.
범인은 개발자 – 최적화된 코드들
• 적절하게 자료구조가 사용되었는가
• 자료구조로 Array와 Set이 사용되었습니다.
• Array는 원본 모델 변환에, Set은 중복제거를 위해 사용되었죠.
• 로직을 봅니다.
• 중복제거된 ID를 도출하는 것에 Set을 사용했으니, 잘 쓴 것 같습니다.
• 그런데… Set으로 도출한 데이터를 제대로 쓴 걸까요?
• Array의 데이터를 Set에 할당하면서 중복제거를 같이 했으면 어땠을까요?
범인은 개발자 – 최적화된 코드들
• 작성된 로직의 복잡도는 어떻게 되는가
• 작성한 로직의 복잡도 계산은 했나요?
• 로직을 봅니다.
• N을 할당해보겠습니다. 최악의 경우를 상정하고, 계수를 제외한 반복 횟수만 세어보겠습니다.
• 앞에서 데이터는 10배 증가했는데, 속도는 100배 증가했죠.
• 아래 로직의 복잡도는, Ο 𝑁# . 네, 그래서 100배 증가한거죠.
+N
+N +N
+N
= 	Ν+Ν+Ν(Ν) = 2Ν + N#
범인은 개발자 – 최적화된 코드들
• 범인은 누구?
• 개.발.자. 사용자와 코드는 죄가 없습니다, 여러분.
• 개선해보겠습니다.
1) Set에 Model을 할당하기 위해 Hashable 구현
2) ID가 같으면 같은 데이터로 판단해야하므로, hashValue 구현
1) Model 변환 후, 바로 Set에 Model을 할당
2) Set.update를 활용하여 날짜값을 기준으로 중복제거 구현
3) 반복은 Model 변환에서 N, Set에 할당하면서 N = 2N
4) 복잡도는? Ο 𝑁
해피엔딩 – 슬기로운 생활들
• 개선 결과
• 개선 전, 10000개 기준 32초
• 개선 후, 10000개 기준 0.08초. 400배 빨라졌습니다.
• 슬기로운 생활을 위해, 다시 한번.
• 구현된 로직이 동작할 환경은 어떤가
• 적절하게 자료구조가 사용되었는가
• 작성된 로직의 복잡도는 어떻게 되는가
• 겉모습에 넘어가지 않도록 주의합시다.
• 그렇게 안 생겼지만 map, filter. 전부 반복문입니다.
• 보기엔 좋아보여도, 그 안에 폭탄이 심어져있을 수 있습니다.
• 테스트 데이터의 기준엔 항상 근거가 있어야 합니다.
• 추정하여 잡은 테스트 데이터의 양은 오류 검증만 할 수 있습니다.
• 정확한 환경 파악이 어렵다면, 복잡도 계산이 큰 도움을 줄 수 있습니다.
해피엔딩 – 슬기로운 생활들
• 오해를 방지하기 위해서
• map. filter. 사용하지 말란 뜻이 아닙니다. 개선된 코드에서도 쓰고 있어요.
• 코드의 모습을 깔끔하게 다듬는 과정에서 조심하자는 거죠.
• 슬기로운 생활을 위한 행동양식
• 복잡도	계산을	꼭	하세요. 복잡도	Ο 𝑁# 는 재앙을 불러옵니다. 제거하세요.
• 적절한 자료구조를 사용하면 복잡도를 낮출 수 있습니다. 단, 메모리 사용량을 신경쓰세요.
• 많은 양의 데이터를 다룰 땐, Autoreleasepool을 사용해보세요. 생각보다 효과적입니다.
• 때론 계수도 부담스러울 때가 있습니다. 반복문을 합치는 것을 고려해보세요.
• 코드의 간결함, 가독성 모두 중요하지만 기본적으로 성능이 보장되어야 합니다.
• 작성된 로직은 실제 환경과 비슷한 테스트가 필요합니다. 사용자 환경 파악, 중요합니다.
부록 – Set의 재조명
• Array와 Dictionary만 주목받는 세상…
• 여러분 Set도 있어요!
• Dictionary와 비슷하지만, Hashable Key: Value가 아닌 Hashable Value로만 구성된 자료구조
• 조건만 맞으면, Dictionary에 비해 더 깔끔한 코드를 작성할 수 있습니다.
• Set은 Hashable 하기 나름입니다.
• public func hash(into hasher: inout Hasher) 내부 구현에 따라 동일 객체 판단을 결정할 수 있습니다.
• 객체의 일부 값만 hashValue 생성에 활용하는 방식으로 가능해요.
• Set.insert, Set.update, Set.remove 차이점을 알자.
• Insert는 동일한 값이 있으면, 값이 Set에 들어가지 않아요.
• 그리고 그 시점에 Set에 추가를 시도한 값이 반환되죠.
• 만약 값이 들어갔다면? nil이 반환됩니다.
• Update는 동일한 값이 있어도, 값이 Set에 들어가요.
• 대신 기존에 있던 값이 반환되죠.
• 만약 동일한 값이 없었다면? nil이 반환됩니다.
• Remove는 값을 삭제합니다.
• 값이 삭제되었다면? 삭제된 값이 반환됩니다.
• 값이 없었다면? nil이 반환됩니다.
• Subtract, Intersection… 필요한 경우, 집합연산은 정말 유용합니다.
200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활

More Related Content

200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활

  • 2. NAVER WEBTOON | 장수한 성능을고민하는슬기로운개발자생활
  • 3. 케이스 스터디 – 일상적인 코드들 • 다음과 같은 기능을 수행하는 코드가 필요합니다. • 사용자들이 작성한 메모를 로컬 DB에서 가져온 뒤 뷰모델로 변환하고, • 유효하지 않은 데이터를 삭제하고 식별자가 같으면 가장 최근의 메모를 표시한다. • 변환된 메모 데이터는 생성날짜를 기준으로 내림차순 정렬되어 사용자에게 제공된다. • 다음과 같은 기능 구현이 필요합니다. • 로컬 DB로부터 가져온 N개의 데이터를 모델로 변환하여, • 데이터 유효성 검사를 수행하여 유효하지 않은 모델을 제거하고, 모델 전체에 대한 중복제거를 진행한 뒤 • 시간을 기준으로 내림차순 정렬하여 반환한다. • 로컬 DB에 저장된 데이터는 다음과 같이 구성되어 있습니다. • [{id: Int?, content: String?, date: Date?}] • 데이터 유효성 검사는 다음과 같이 진행합니다. • 3개의 필드 중 하나라도 값이 없으면 유효하지 않은 데이터로 판단한다. • 데이터 중복제거는 다음과 같이 진행합니다. • ID는 유일해야 한다. ID가 중복되는 경우 시간 정보가 큰 값(최근값)을 사용한다.
  • 4. 케이스 스터디 – 일상적인 코드들 • 일단 모델을 구현해보겠습니다.
  • 5. 케이스 스터디 – 일상적인 코드들 • 앞의 코드를 활용하여 직독직해 수준의 구현을 해보겠습니다.
  • 6. 케이스 스터디 – 일상적인 코드들 • 코드를 다듬어볼께요.
  • 7. N의 습격 – 비일상적인 환경들 • 변화된 코드를 보면? • for-in 대신 map, filter를 사용하여 반복문을 깔끔하게 정리했습니다. • 각각의 기능이 map, filter에 함수로 할당되어 코드가 대폭 줄어들었습니다. • 결론. 겉으로 보기에 코드가 깔끔해졌고, 트렌디하며, 한마디로 뭔가 있어보입니다. • 무엇이 문제인가? • 앞의 코드는 개발자가 반드시 고려해야하는 부분을 처음부터 고려하지 않은 코드입니다. • 이를 무시하고, 코드의 모양을 아름답게 개선하여 문제는 더 깊게 숨겨졌습니다. • 개발자가 반드시 고려해야할 사항 • 구현된 로직이 동작할 환경은 어떤가 • 적절하게 자료구조가 사용되었는가 • 작성된 로직의 복잡도는 어떻게 되는가 • 해당 코드가 포함되어 배포되었고, 메모를 10000개 가지고 있는 사용자와 만났습니다. • …그리고 앱 실행 후, 메모를 보기 위해 사용자는 30초 이상의 시간을 기다려야 합니다. 이것을 고려하지 않으면, 개발자는 재앙을 맞이하게 됩니다.
  • 8. N의 습격 – 비일상적인 환경들 • 정말 비일상적인 환경일까요? • 메모를 10000개나 가지고 있는 사용자가 있긴한가요? • 그리고, 10000개나 가지고 있으니까 당연히 느린거 아닌가요? • …정말 그럴까요? • 하나씩 살펴보겠습니다. • 컴파일 오류가 없었고, 테스트 데이터를 넣었을 때 원하는 결과도 나왔죠. • UI와 기능을 연결해서, 데이터가 잘 표시되는지도 확인했을 겁니다. • 하지만 이건 환경이 아닙니다. 그저, 로직이 동작하는지 확인한거죠. • 개발자가 반드시 고려해야할 사항, 챙기셨나요? • 구현된 로직이 동작할 환경은 어떤가 • 적절하게 자료구조가 사용되었는가 • 작성된 로직의 복잡도는 어떻게 되는가
  • 9. 범인은 개발자 – 최적화된 코드들 • 구현된 로직이 동작할 환경은 어떤가 • 로직 구현 후, 테스트 데이터를 100개 정도 만들었습니다. • 속도 측정을 해보니, 0.004초(시뮬레이터 기준). 속도 문제가 없네요. • 그런데 왜 테스트 데이터 100개죠? • 정말 사용자들은 그 정도의 데이터만 가지고 있을까요? • 조금 더 신경을 써서, 1000개의 메모를 가지고 있는 사용자를 고려해봅니다. • 속도 측정을 해보니, 0.4초(시뮬레이터 기준). 속도 문제가 없네요. • 정말 속도 문제가 없을까요? • 데이터는 10배 증가했는데, 왜 속도는 100배나 증가했을까요? • 그리고 무엇보다, 사용자 데이터는 추정하면 안 됩니다. • 여건이 허락하는 선에서, 사용자의 환경이 어떤지 알고 있어야 합니다.
  • 10. 범인은 개발자 – 최적화된 코드들 • 적절하게 자료구조가 사용되었는가 • 자료구조로 Array와 Set이 사용되었습니다. • Array는 원본 모델 변환에, Set은 중복제거를 위해 사용되었죠. • 로직을 봅니다. • 중복제거된 ID를 도출하는 것에 Set을 사용했으니, 잘 쓴 것 같습니다. • 그런데… Set으로 도출한 데이터를 제대로 쓴 걸까요? • Array의 데이터를 Set에 할당하면서 중복제거를 같이 했으면 어땠을까요?
  • 11. 범인은 개발자 – 최적화된 코드들 • 작성된 로직의 복잡도는 어떻게 되는가 • 작성한 로직의 복잡도 계산은 했나요? • 로직을 봅니다. • N을 할당해보겠습니다. 최악의 경우를 상정하고, 계수를 제외한 반복 횟수만 세어보겠습니다. • 앞에서 데이터는 10배 증가했는데, 속도는 100배 증가했죠. • 아래 로직의 복잡도는, Ο 𝑁# . 네, 그래서 100배 증가한거죠. +N +N +N +N = Ν+Ν+Ν(Ν) = 2Ν + N#
  • 12. 범인은 개발자 – 최적화된 코드들 • 범인은 누구? • 개.발.자. 사용자와 코드는 죄가 없습니다, 여러분. • 개선해보겠습니다. 1) Set에 Model을 할당하기 위해 Hashable 구현 2) ID가 같으면 같은 데이터로 판단해야하므로, hashValue 구현 1) Model 변환 후, 바로 Set에 Model을 할당 2) Set.update를 활용하여 날짜값을 기준으로 중복제거 구현 3) 반복은 Model 변환에서 N, Set에 할당하면서 N = 2N 4) 복잡도는? Ο 𝑁
  • 13. 해피엔딩 – 슬기로운 생활들 • 개선 결과 • 개선 전, 10000개 기준 32초 • 개선 후, 10000개 기준 0.08초. 400배 빨라졌습니다. • 슬기로운 생활을 위해, 다시 한번. • 구현된 로직이 동작할 환경은 어떤가 • 적절하게 자료구조가 사용되었는가 • 작성된 로직의 복잡도는 어떻게 되는가 • 겉모습에 넘어가지 않도록 주의합시다. • 그렇게 안 생겼지만 map, filter. 전부 반복문입니다. • 보기엔 좋아보여도, 그 안에 폭탄이 심어져있을 수 있습니다. • 테스트 데이터의 기준엔 항상 근거가 있어야 합니다. • 추정하여 잡은 테스트 데이터의 양은 오류 검증만 할 수 있습니다. • 정확한 환경 파악이 어렵다면, 복잡도 계산이 큰 도움을 줄 수 있습니다.
  • 14. 해피엔딩 – 슬기로운 생활들 • 오해를 방지하기 위해서 • map. filter. 사용하지 말란 뜻이 아닙니다. 개선된 코드에서도 쓰고 있어요. • 코드의 모습을 깔끔하게 다듬는 과정에서 조심하자는 거죠. • 슬기로운 생활을 위한 행동양식 • 복잡도 계산을 꼭 하세요. 복잡도 Ο 𝑁# 는 재앙을 불러옵니다. 제거하세요. • 적절한 자료구조를 사용하면 복잡도를 낮출 수 있습니다. 단, 메모리 사용량을 신경쓰세요. • 많은 양의 데이터를 다룰 땐, Autoreleasepool을 사용해보세요. 생각보다 효과적입니다. • 때론 계수도 부담스러울 때가 있습니다. 반복문을 합치는 것을 고려해보세요. • 코드의 간결함, 가독성 모두 중요하지만 기본적으로 성능이 보장되어야 합니다. • 작성된 로직은 실제 환경과 비슷한 테스트가 필요합니다. 사용자 환경 파악, 중요합니다.
  • 15. 부록 – Set의 재조명 • Array와 Dictionary만 주목받는 세상… • 여러분 Set도 있어요! • Dictionary와 비슷하지만, Hashable Key: Value가 아닌 Hashable Value로만 구성된 자료구조 • 조건만 맞으면, Dictionary에 비해 더 깔끔한 코드를 작성할 수 있습니다. • Set은 Hashable 하기 나름입니다. • public func hash(into hasher: inout Hasher) 내부 구현에 따라 동일 객체 판단을 결정할 수 있습니다. • 객체의 일부 값만 hashValue 생성에 활용하는 방식으로 가능해요. • Set.insert, Set.update, Set.remove 차이점을 알자. • Insert는 동일한 값이 있으면, 값이 Set에 들어가지 않아요. • 그리고 그 시점에 Set에 추가를 시도한 값이 반환되죠. • 만약 값이 들어갔다면? nil이 반환됩니다. • Update는 동일한 값이 있어도, 값이 Set에 들어가요. • 대신 기존에 있던 값이 반환되죠. • 만약 동일한 값이 없었다면? nil이 반환됩니다. • Remove는 값을 삭제합니다. • 값이 삭제되었다면? 삭제된 값이 반환됩니다. • 값이 없었다면? nil이 반환됩니다. • Subtract, Intersection… 필요한 경우, 집합연산은 정말 유용합니다.