4. LINQ
Language-Integrated Query
데이터 저장소 종류와 관계 없이 저장소에 일관성 있는 인터페이스로 질의
LINQ to Objects, LINQ to SQL, LINQ to XML ...
본 세션에서는 LINQ to Objects 관련 내용만을 다룹니다.
5. LINQ
int[] numbers = { 5, 10, 8, 3, 6, 12 };
Query syntax
var results = from num in numbers
where num % 2 == 0
orderby num
select num * 2;
foreach(var result in results)
Console.WriteLine(result);
Method syntax
var results = numbers
.Where(num => num % 2 == 0)
.OrderBy(num => num)
.Select(num => num * 2);
foreach(var result in results)
Console.WriteLine(result);
10. LINQ
동일한 인터페이스 사용 - 모든 컬렉션 류, 그 외 다른 종류의 저장소에도 동일함.
다양하고 편리한 기능 - Max, Min, Average, OrderBy, First, FirstOrDefault, Last, LastOrDefault …
지연 실행 - 성능 이점
간결한 High Level 코드 품질 - 유지 보수 용이
컴파일 타임 에러 체크 – SQL 시 문자열 쿼리의 체크를 컴파일 타임에 가능
12. LINQ
Galaxy S3 100개 1,000개 10,000개 100,000개
Non LINQ 0 ms 0 ms 4 ms 42 ms
LINQ 0 ms 2 ms 26 ms 250 ms
iPhone 6+ 100개 1,000개 10,000개 100,000개
Non LINQ 0 ms 0 ms 3 ms 16 ms
LINQ 0 ms 1 ms 10 ms 37 ms
‘기획서의 요구사항’ 성능 테스트
13. LINQ
디바이스 성능은 계속 발전
- 차이가 점점 좁혀진다.
80-20 법칙
- 최적화가 필요한 곳만 코드를 풀어 쓰자
클라이언트 사이드에서 대용량의 데이터를 사용하는 곳은 많지 않다.
- 100,000개의 요소를 가진 컬렉션은 거의 없다.
15. LINQ on iOS
List<string> strs = new List<string> { “0”, “1”, “2”, “3” };
string first = strs.FirstOrDefault ();
Debug.Log(first); // “0"
문제 없음
16. LINQ on iOS
List<int> numbers = new List<int> { 0, 1, 2, 3 };
int first = numbers.FirstOrDefault ();
Debug.Log(first.ToString());
ExecutionEngineException: Attempting to JIT compile method
'System.Linq.Enumerable/PredicateOf`1<int>:.cctor ()' while running with --aot-only.
Rethrow as TypeInitializationException: An exception was thrown by the type initializer for
PredicateOf`1
17. LINQ on iOS
LINQ 코드는 System.Core.dll에 존재하여 디버깅도 못해보고
많은 Unity 사용자들은 LINQ 사용을 포기
19. public static class Enumerable
{
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source)
{
return First (source, PredicateOf<TSource>.Always, Fallback.Default);
}
private static TSource First<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, Fallback fallback)
{
foreach (TSource source1 in source)
{
if (predicate(source1))
return source1;
}
if (fallback == Fallback.Throw)
throw new InvalidOperationException();
return default(TSource);
}
private enum Fallback
{
Default,
Throw,
}
private class PredicateOf<T>
{
public static readonly Func<T, bool> Always = (t => true);
}
}
IEnumerable<int> source
Func<int, bool> predicateIEnumerable<int> source
PredicateOf<int>.Always 이 녀석이 문제
20. public static class Enumerable
{
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source)
{
return First (source, PredicateOf<TSource>.Always, Fallback.Default);
}
private class PredicateOf<T>
{
public static readonly Func<T, bool> Always = (t => true);
}
…
}
예외가 발생하지 않는다.
void _unusedMethod() // type hint, 해당 메소드는 어디서도 호출 되지 않습니다.
{
new PredicateOf<int>();
}
21. C# 실행 환경
C#
Source Code
IL
(Intermediate
Language)
Machine Code
C# Compiler JIT Compiler
RuntimeCompile time
C#
Source Code
IL
(Intermediate
Language)
Machine Code
C# Compiler AOT Compiler
Compile time
JIT
AOT iOS에서의 구동환경
26. Generic Sharing
AOT의 Generic Type Parameter가 Value Type일 때만
문제가 발생하는 이유는 이와 같이 다른 메커니즘을 지니기 때문
또한 상대적으로 Reference Type에 비해 Value Type은
Generic Type Parameter 마다 코드를 생성해야 하기 때문에
AOT의 경우 코드가 누락될 가능성이 생김.
이 페이지의 내용은 발표자의 가설입니다.
27. Exception
ExecutionEngineException: Attempting to JIT compile method
'System.Linq.Enumerable/PredicateOf`1<int>:.cctor ()' while running with --aot-only.
Rethrow as TypeInitializationException: An exception was thrown by the type initializer for
PredicateOf`1
System.Linq.Enumerable/PredicateOf<int>의 Class Constructor(cctor) 메소드를 찾을 수 없어
JIT Compile를 시도하다가 난 예외
28. Exception
몇 몇 복잡한 상황에서 AOT 컴파일러가 Generic 타입을 캐치하지 못한 채 실행 파일을 생성.
따라서 이전에 언급한 구체적인 타입에 대한 힌트를 주게 되면 AOT 컴파일러는 해당 타입에
대한 코드를 미리 생성 가능.
void _unusedMethod() // type hint
{
new PredicateOf<int>();
}
29. LINQ in Mono 3.x
#if !FULL_AOT_RUNTIME
private class PredicateOf<T>
{
public static readonly Func<T, bool> Always = (Func<T, bool>)(t => true);
}
#endif
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source)
{
#if !FULL_AOT_RUNTIME
return First<TSource>(source, PredicateOf<TSource>.Always, Fallback.Default);
#endif
foreach(var element in source)
return element;
return default(TSource);
}
Mono 3.x는 해당 버그를 우회했다.
유니티는 Mono 2.x를 사용
30. 그 외…
몇 몇 메소드의 경우 특정 상황에서 Lambda 또는 익명 메소드를 AOT 컴파일 하지 못한다.(Generic과 무관)
해당 Lambda 또는 익명 메소드를 멤버 메소드로 바꾸면 예외가 사라진다.
public static double Average (this IEnumerable<int> source, Func<TSource, int> selector)
{
return source.Select(selector).Average<int, long, double> (source, (a, b) => a + b, (a, b) => (double) a / (double) b);
}
public static double AverageModified(this IEnumerable<int> source, Func<TSource, int> selector)
{
return source.Select(selector).Average<int, long, double> (source, Func1, Func2); // Lambda를 멤버 메소드로 대체
}
static long Func1(long a, int b)
{
return a + b;
}
static double Func2(long a, long b)
{
return (double)a / (double)b;
}
실제 발표 시 해당 페이지의 일부 내용이 잘못 표기 되어 수정되었습니다.
이 메소드 내부에 사용한 람다식의 코드를
생성하지 못한다.
ex> Action action = () => Debug.Log(“Foo”);
action();
31. 그 외…
그 외에도 여러 가지 패턴의 AOT 관련 예외가 있으며 자세한 사항은 아래의 링크에서 확인.
- http://neue.cc/2014/07/01_474.html
44. LINQ with IL2CPP
// Bulk_Generics3.cpp
// System.Void System.Linq.Enumerable/PredicateOf`1<System.Int32>::.cctor()
extern "C" void PredicateOf_1__cctor_m686174972_gshared (Il2CppObject * __this /* static, unused */, const MethodInfo* method)
{
…
}
// System.Boolean System.Linq.Enumerable/PredicateOf`1<System.Int32>::<Always>m__76(T)
extern "C" bool PredicateOf_1_U3CAlwaysU3Em__76_m1746297546_gshared (Il2CppObject * __this /* static, unused */, int32_t ___t,
const MethodInfo* method)
{
{
return (bool)1;
}
}
Mono AOT로 컴파일되 지 않았던 메소드가
잘 생성이 되어 있는 걸 알 수 있다.
45. 결론
IL2CPP를 사용. (iOS 64bit 지원을 위해서는 반드시 사용)
만약 낮은 버전의 Unity를 사용해야 한다면 Third party library 사용
그래도 뭔가 찜찜하고 불안하면 IL2CPP에서도 소스 코드 수정이 가능한 Third party library 사용
사실 Unity에서 LINQ를 사용하지 못한다는 편견을 깨고 싶었습니다.