当前位置:网站首页>Optimal solution for cold start
Optimal solution for cold start
2022-06-25 12:41:00 【I'm Wong Tai Sin】
List of articles
1. background
A while ago, I made a demand , This is to solve the problem of slow startup of one of our modules , After investigation, it is found that the task execution time of our core path is relatively long . We came up with an optimization method , Is in the App When starting, start a low priority thread to preload tasks , When users really use this module , The startup time will be greatly shortened .
However, in the application mr When , Questioned by the basic colleagues , It is no longer allowed to add tasks in the startup phase , If you have to add , You must apply by email . That's how it feels to me , This App You didn't write it , You can't realize what you want ( Actually, the experience was terrible ), Maybe it will be like this when the team is big , Then we found another time to preload , Avoid adding tasks in the startup phase .
In the process of solving this problem , I found out , Our task startup code is poorly written , This reminds me of the previous cold start optimization , Do a startup framework , It can help us reasonably arrange the startup task , And monitor the time of each task and the overall execution time , Prevent deterioration .
I reuse it kotlin Write it over , Share in github On , I named it StartUp .
https://github.com/bearhuang-omg/startup
2. Usage mode
Before introducing how to use , You need to know the following classes first :
Several important classes :
| class | explain |
|---|---|
| TaskDirector | Task director class , Depending on the task interdependencies and priorities , Arrange the execution sequence of tasks , It's also sdk Entrance |
| Priority | Define the priority of the task , There are four priorities , Namely IO( stay io Execute on the thread pool ),Calculate( Execute on the compute thread pool ),Idle( Execute when idle ),Main( Main thread execution ) |
| Task | The task class , Priority can be specified , Specify the task name on which it depends , Be careful : The task name cannot be duplicate |
| IDirectorListener | The life cycle of task execution , Include :onStart , onFinished,onError |
After resealing , The interface provided is very simple
Usage mode :
| Interface | Parameters | Return value | remarks |
|---|---|---|---|
| addTask | task:Task // Mission | TaskDirector | return TaskDirector, You can add tasks in a chain |
| registerListener | IDirectorListener | nothing | Monitor task execution |
| unRegisterListener | IDirectorListener | nothing | Unregister listening |
| start | nothing | nothing | Start task execution |
Example :
// Create tasks
val task2 = object:Task() {
override fun execute() {
Thread.sleep(1000)
}
override fun getName(): String {
return "tttt2"
}
override fun getDepends(): Set<String> {
return setOf("tttt1")
}
}
// Create task Director
val director = TaskDirector()
// Listening task lifecycle
director.registerListener(object : IDirectorListener{
override fun onStart() {
Log.i(TAG,"director start")
}
override fun onFinished(time: Long) {
Log.i(TAG,"director finished with time ${
time}")
}
override fun onError(code: Int, msg: String) {
Log.i(TAG,"errorCode:${
code},msg:${
msg}")
}
})
// Add tasks
director.apply {
addTask(task1)
addTask(task2)
addTask(task3)
}
// Start execution
director.start()
3. The basic principle
We used to do cold start optimization , Some pain points in the start-up phase are summarized :
- The code is a mess , Can not clearly know what is necessary , What is not necessary ;
- If the task has dependencies , If you don't add notes , It's easy to be modified by those who follow , Make a mistake ;
- It is impossible to know exactly how long the task will take , It is difficult to determine the optimization direction ;
For these pain points , We did the following :
1. Abstract it into a task diagram
We encapsulate the relatively independent processes in the startup phase into individual processes task, And you can specify its priority and task dependency , If there is no dependency, it is directly attached to the root node .
such as , There are ABCDE Five tasks , among A,B Does not depend on any task ,C Depend on A,D Depend on AB,E Depend on CD. Therefore, the generated task graph is as follows :
Creating task dependencies is also very simple , With ABC For example :
// Create tasks A
val taskA = object:Task() {
override fun execute() {
}
override fun getName(): String {
return "A"
}
}
// Create tasks B
val taskB = object:Task() {
override fun execute() {
}
override fun getName(): String {
return "B"
}
}
// Create tasks C
val taskC = object:Task() {
override fun execute() {
}
override fun getName(): String {
return "C"
}
// Depending on the task A and B
override fun getDepends(): Set<String> {
return setOf("A","B")
}
}
among getName Methods do not have to be duplicated , If there is no replication , The framework will automatically generate a unique name.
2. Check whether there are rings
When the task graph is generated , Naturally, we will encounter the following two problems :
- The dependent task is not in the task diagram ;
- There are rings in the generated task graph ;
First, let's look at the first question :
Every time we call addTask After the interface , The framework saves the task in the task map among , among key For the task name, If you find that the task you depend on is not map among , Will immediately call back the lifecycle onError Interface .
// Mission map
private val taskMap = HashMap<String, TaskNode>()
// Life cycle onError Interface
fun onError(code: Int, msg: String)
Let's look at the second question ,
If there are rings in the task diagram , Then the tasks will be interdependent , The task cannot be executed correctly .
There are rings in the task diagram , It can be divided into the following two cases :
1. The task ring is independent of Root Outside the node 
2. The task loop is not independent of Root Outside the node 
The main inspection process is as follows :
- from Root Node departure , Add non dependent tasks to the queue in turn ;
- Each time the current task is taken from the queue and moved out of the queue , Reduce the number of dependencies of its subtasks 1, If the number of dependencies of its subtasks is less than or equal to 0, The subtask is also added to the queue ;
- repeat 2, Until the queue is empty ;
- If the task loop is not independent of Root node , In the process of traversal, a task has been moved out of the queue , A subsequent subtask adds it to the queue , At this point, it can be judged that there is a ring ;
- If the task ring is independent of Root node , After the queue is empty , There are still tasks that have not been traversed .
- If not 4,5 Two cases , Then it can be determined that there is no ring in the task diagram .
The code implementation is as follows :
private fun checkCycle(): Boolean {
val tempQueue = ConcurrentLinkedDeque<TaskNode>() // The record has been ready The task of
tempQueue.offer(rootNode)
val tempMap = HashMap<String, TaskNode>() // All current tasks
val dependsMap = HashMap<String, Int>() // The number of tasks on which all tasks depend
taskMap.forEach {
tempMap[it.key] = it.value
dependsMap[it.key] = it.value.task.getDepends().size
}
while (tempQueue.isNotEmpty()) {
val node = tempQueue.poll()
if (!tempMap.containsKey(node.key)) {
Log.i(TAG, "task has cycle ${
node.key}")
directorListener.forEach {
it.onError(Constant.HASH_CYCLE, "TASK HAS CYCLE! ${
node.key}")
}
return false
}
tempMap.remove(node.key)
if (node.next.isNotEmpty()) {
node.next.forEach {
if (dependsMap.containsKey(it.key)) {
var dependsCount = dependsMap[it.key]!!
dependsCount -= 1
dependsMap[it.key] = dependsCount
if (dependsCount <= 0) {
tempQueue.offer(it)
}
}
}
}
}
if (tempMap.isNotEmpty()) {
Log.i(TAG, "has cycle,tasks:${
tempMap.keys}")
directorListener.forEach {
it.onError(Constant.HASH_CYCLE, "SEPERATE FROM THE ROOT! ${
tempMap.keys}")
}
return false
}
return true
}
3. Let tasks execute in different thread pools
After the task check is legal , Then you can start to execute happily , Different tasks will be thrown to different thread pools for execution according to their priority .
when (task.getPriority()) {
Priority.Calculate -> calculatePool.submit(task)
Priority.IO -> ioPool.submit(task)
Priority.Main -> mainHandler.post(task)
Priority.Idle -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Looper.getMainLooper().queue.addIdleHandler {
task.run()
return@addIdleHandler false
}
} else {
ioPool.submit(task)
}
}
}
After each task is completed , It will automatically trigger the execution of its subtasks , The subtask will determine the number of dependencies of the current task , When the dependent quantity is 0 when , It can be really implemented .
There is actually a concurrency problem here , For example, tasks C Depend on A and B, and A and B Execute on different threads , When A and B After the execution is complete , Trigger at the same time C perform , May lead to inconsistent changes in the number of dependencies , Problems arise . The previous solution was to lock , Now I put all these scheduled tasks in TaskDirector Execute in a separate thread in , Avoid the problem of concurrency , And there is no need to lock .
private fun runTaskAfter(name: String) {
// TaskDirector Independent threads of , Avoid concurrent problems
handler.post {
finishedTasks++
// Record the time when the task was executed
if (timeMonitor.containsKey(name)) {
timeMonitor[name] = System.currentTimeMillis() - timeMonitor[name]!!
}
// After the execution of a single task , Trigger the next task execution
if (taskMap.containsKey(name) && taskMap[name]!!.next.isNotEmpty()) {
taskMap[name]!!.next.forEach {
taskNode ->
taskNode.start()
}
taskMap.remove(name)
}
Log.i(TAG, "finished task:${
name},tasksum:${
taskSum},finishedTasks:${
finishedTasks}")
// After all tasks are completed , Trigger director Callback
if (finishedTasks == taskSum) {
val endTime = System.currentTimeMillis()
if (timeMonitor.containsKey(WHOLE_TASK)) {
timeMonitor[WHOLE_TASK] = endTime - timeMonitor[WHOLE_TASK]!!
}
Log.i(TAG, "finished All task , time:${
timeMonitor}")
runDirectorAfter()
}
}
}
4. Monitoring and anti degradation
Preventing deterioration is a very important problem , We worked hard to optimize for a long time , It turned out that there were few versions , The start-up time has slowed down again , This is too big for me .
For each task, We all added monitoring , Automatically monitor the execution time of each task , And the overall execution time of all tasks . After the task is completed , Report at a certain time , In this way, the startup process can be monitored from time to time .
abstract class Task : Runnable {
......
final override fun run() {
Log.i(TAG,"start ${
getName()}")
before.forEach {
it(getName())
}
execute()
after.forEach {
it(getName())
}
Log.i(TAG,"end ${
getName()}")
}
......
}
4. summary
The cold start scenario has a great impact on the user experience , It's also very good to have a dedicated colleague monitoring on the basic side , But I think the important thing is to be sparse , Instead of blocking , How to load on demand is what we pursue , Not one size fits all , Directly remove the top leaders , Let the business side be tied up .
边栏推荐
- When MySQL queries fields in JSON format, it takes a property value of JSON data
- How can we differ LEFT OUTER JOIN vs Left Join [duplicate]
- Lighten the source code -- lighten the app system development function introduction to the beautiful world lighten the app system development source code in China
- sudo: ulimit: command not found
- Initialize the project using the express framework
- Is it safe to open an account and buy stocks on the Internet?
- ECSHOP product attribute color specification size stock item No. automatic combination
- Kotlin Foundation
- Singleton mode in PHP to reduce memory consumption
- Why do we do autocorrelation analysis? Explain application scenarios and specific operations
猜你喜欢

Full nanny tutorial of Market Research Competition (experience sharing)

A commonly used statistical modeling method -- difference analysis

百度搜索稳定性问题分析的故事

High performance + million level Excel data import and export

初识CANOpen

What is the primordial universe

一篇文章讲清楚MySQL的聚簇/联合/覆盖索引、回表、索引下推

Installation and removal of MySQL under Windows

Zhangxiaobai's way of penetration (V) -- detailed explanation of upload vulnerability and parsing vulnerability

mysql FIND_ IN_ Set function
随机推荐
Upgrade opsenssh to 8.8p1
mysql FIND_ IN_ Set function
Guess Tongyuan B
VIM common commands and shortcut keys
GNSS receiver technology and application review
Mind mapping video
Thinkphp3 use phpword to modify the template and download it
三入职场!你可以从我身上学到这些(附毕业Vlog)
为何数据库也云原生了?
Windows下MySQL的安装和删除
PHP parsing QR code content
Navicat premium view password scheme
sudo: ulimit: command not found
Lighten the source code -- lighten the app system development function introduction to the beautiful world lighten the app system development source code in China
(2) Pyqt5 tutorial -- > using qtdesigner to separate interface code
Foreach method of array in JS
(6) Pyqt5--- > window jump (registration login function)
Laravel is not logged in and cannot access the background by entering the URL
Penetration tool environment - installing sqli labs in centos7 environment
Laravel task scheduling