스터디 8주차 zygote 성능 개선 시도1
저번주 필자가 건드린 enableLazyPreload 변수로 인하여 앱의 초기 시작 속도가 굉장히 빨라지는 것을 체험하였다. 그리고 회의결과 만약 해당 변수가 true일 때가 성능이 빠르다면 왜 false로 해놨는지에 대한 의문점을 가지고 process의 개수에 따라서 해당 옵션을 true or false로 조절할 수 있는지 알아보는 임무가 주어졌다.
그래서 이번 포스트는
-
Zygote 내에서 현재 구동 중인 Process의 갯수를 알아내서 enableLazyPreload를 false에서 true로 고치는 방법
-
해당 방법들을 적용했을 때 얼마만큼의 속도 개선이 있는지 manual하게 측정하는 것이 아닌 systemic하게 측정하는 방법에 대하여 알아볼 것이다.
1. Zygote 내에서 현재 구동 중인 Process의 갯수를 알아내서 enableLazyPreload를 false에서 true로 고치는 방법
구동 중인 AOSP에 adb를 연결하여 shell 명령어로 ps -ef를 실행하면 현재 실행 중인 커널 프로세스를 제외한 모든 프로세스를 출력해준다.
위 스크린샷과 같이 clock 앱을 실행시킨뒤 ps -ef 옵션으로 clock에 해당하는 프로세스를 찾았더니 바로 찾을 수 있었다.
하지만 이것을 실행 중인 Zygote내에서 찾는 것은 조금 다른 얘기이다. 먼저 Zygote의 실행 흐름을 살펴보자.
Process(실행을 요청받은 애플리케이션)의 클래스에서 ZygoteInit 클래스에 connect 요청을 하여 ZygoteConnection 객체를 생성하고 위 그림에서는 runOnce메소드를 이용하여 새로운 Process를 생성한다고 되어 있지만 필자가 설치한 환경인 AOSP 10에서는 ZygoteServer.java에서 ZygoteConnection 객체를 생성한 후에 해당 객체의 processOneCommand() 메소드를 사용하여 process를 실행한다. 이때 사용하는 객체인 ZygoteConnection의 코드인 ZygoteConnection.java를 살펴보면
267번째 줄에서 forkAndSpecialize() 메소드를 이용하여 새로운 Process를 생성하는 것을 볼 수 있다. 그 후 실행 분기가 나눠졌을때 pid가 0이라면 child process의 흐름이라는 의미이므로 zygoteServer의 setForkChild() 메소드를 실행시키는데 이 코드를 ZygoteServer.java에서 확인하면 다음과 같다.
mIsForkChild 변수를 true로 설정하는데 이 변수는
true라면 456번째 줄에서 connection객체의 processOneCommand() 메소드로 돌려받은 command를 return하게 해주는 일종의 flag같은 역할을 한다.
그래서 요약하자면 위 그림과 같이 된다. 즉 command 변수가 ZygoteInit.java에 반환되면 프로세스의 실행이 끝나게 되는 것이다.
근데 위는 실행과정의 끝이라서 우리가 원하는 process개수에 맞춰서 enableLazyPreload를 설정하기엔 늦은 감이 없지않아 있다. 따라서 아예 process의 첫 시작 부분으로 가서 시작하기 전에 process의 개수를 검사하는 코드를 넣을 부분을 찾을 것이다.
Process.java의 위치는 아래와 같다.
1
./frameworks/base/core/java/android/os/Process.java
Process.java에서 start함수를 찾으면 위와 같다. 여기서 알아야할 2가지 사전 지식이 있다.
ProcessStartResult class의 구조와 ZYGOTE_PROCESS의 변수 타입이다.
ProcessStartResult class의 구조는 위와 같다. 그저 pid를 담는 class일 뿐이다.
그리고 ZYGOTE_PROCESS 전역변수는 ZygoteProcess 타입으로 정의되어 있다. 그리고 Process.java의 534번째 줄에서 입력받은 매개변수를 그대로 ZygoteProcess변수의 start 메서드에 넘겨주면서 호출하고 있다. 그렇다면 또다시 ZygoteProcess.java 코드를 보러 가보아야 한다. ZygoteProcess코드는 같은 경로에 존재한다.
ZygoteProcess.java의 start코드이다. 마찬가지로 받은 매개변수를 startViaZygote라는 메소드에 그대로 전달해주고 있다. 그렇다면 startViaZygote() 메소드는 어떤 기능을 하는지 봐야 할 것이다.
startViaZygote또한 전달받은 매개변수들을 가지고 시작을 하는데 맨 처음에 ArrayList로 Zygote를 위한 매개변수를 가공하여 담을 리스트인 argsForZygote를 생성한다.
그리고 위와 같은 코드로 argsForZygote list에 mountExternal 변수가 가지는 값과 같은 값을 가지는 문자열을 추가하고 있다. 이 코드는 if else if 문을 남발하는 코드로 향후에 switch 문으로 리팩토링할 여지가 있어보인다.
그리고 gid들을 콤마로 구분하는 문자열로 만들어서 argsForZygote에 추가한다.
또다시 입력받은 매개변수가 null이 아니라면 각각의 변수가 의미하는 문자열을 argsForZygote에 담아주고 626번째 줄에서 processClass를 추가해준다.
마지막으로 extraArgs도 존재한다면 모든 나머지 인자들을 더하여 argsForZygote이 담아준후 synchronized() 키워드를 쓰고 있다. 이때 동기화할 객체는 mLock이라는 객체인데
위 코드의 주석에서 볼 수 있듯이 이미 ZygoteState의 socket으로 통신할 때 얻어지는 객체이다. 그렇게 쓰레드 동기화작업을 마치면 또다시 zygoteSendArgsAndGetResult라는 메소드를 호출한다. 이때 앞의 두 인자는 별로 중요하지 않은 것 같고 마지막 argsForZygote list를 전달해줌으로써 전달되야할 모든 인자들이 가공된 리스트가 전달되는 것을 볼 수 있다.
전반적으로 봤을 때 startViaZygote메서드는 매개변수로 넘겨받은 인자들을 문자열로 가공하여 리스트에 저장 후 zygoteSendArgsAndGetResult라는 메서드로 넘겨주는 역할을 한다고 볼 수 있다. 그렇다면 zygoteSendArgsAndGetResult 메서드는 무슨 기능을 하는걸까?
…
아주 심플한 코드인데 넘겨받은 args list를 msgStr이라는 문자열 변수에 개행문자로 각각의 인자들을 구별할 수 있는 문자열을 저장한 뒤 zygoteState라는 넘겨받은 매개변수와 msgStr을 또다시 attempZygoteSendArgsAndGetResult 메서드로 넘겨주고 있다. 그렇다면 이 메서드도 무슨 기능을 하는지 볼 것이다.
이번에는 Process클래스의 ProcessStartResult 클래스 타입의 result라는 변수를 선언하고 해당 변수에 pid와 usingWrapper를 읽어서 만약 해당 pid가 음수라면 error를 throw하고 그게 아니라면 result 변수를 반환하고 있다.
이를 그림으로 표현하면 다음과 같다.
이제 우리가 시작하는 부분은 알게되었으니 처음 시작하는 부분(Process.java의 start())에서 process의 개수를 검사하고 만약 process의 개수가 임계값을 넘는다면 Zygote를 재시작하는데 이때 enableLazyPreload 변수의 값을 바꿔서 재시작을 한다. 그리고 나서 다시 Process의 실행흐름을 진행시키면 될 것이다.
이를 위해 필요한 지식은 다음과 같다.
-
Android os에서 현재 실행 중인 Process의 개수를 알아내는 코드
-
우리가 제작한 코드를 모듈형태로 Process.java의 start함수의 맨 처음 부분에 이식하는 방법
-
Zygote를 kill하고 Zygote를 restart하는 방법
-
Zygote를 restart할 때 enableLazyPreload 변수의 값을 지정하는 방법
-
현재 실행되고 있는 Zygote Process의 enableLazyPreload 변수값을 확인하는 방법
1은 Android studio에서 직접 코딩을 하여 해당 기능을 해주는 class와 method를 찾으면 될 것이고 2는 같은 package 내의 java 코드를 쓸 수 있겠끔 import를 해주면 될 것 같은데 나머지 3,4,5번은 어떻게 해야할지 감이 안잡힌다. 먼저 Zygote는 init process가 처음 실행될 때 시작되는 process인데 zygote를 restart하자고 init process를 구동시키면 os 자체가 꺼질 수도 있기 때문에 조금 더 연구를 해보아야 겠다.
2. 앱 실행 속도 개선을 systemic하게 측정하는 방법
이 부분은 앱을 실행시켰을 때 앱이 시작하는 순간부터 실제 process에 load되기 까지의 걸리는 시간을 측정해야 한다. 이 부분 또한 앱이 시작하는 부분에서 timer를 시작하고 앱이 끝나는 부분에서 timer를 꺼서 나온 timer를 출력해주면 될 것이다. 근데 여기서 문제가 하나 있다. 과연 Process.java의 start에서 시작된 timer 객체를 어떻게 ZygoteInit.java의 main에 까지 전달하느냐이다.
이 부분 또한 해당 소스의 함수부분에서는 현재 시간 측정만 하고 측정된 값을 다른 모듈로 넘겨주어서 해당 모듈에서 시간차를 구하고 결과를 Log file에 출력해주는 형식으로 구할 것이다. 이때 필요한 것이 process 실행 요청이 들어오는 시간과 해당 프로세스의 패키지 네임등의 정보이다. 이 정보들을 Log file에 기입하면 여러번의 실행 속도 측정을 조금 더 정확하게 구분할 수 있게 될 것이기 때문이다.