当前位置:网站首页>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 .
边栏推荐
- Mysql database logs binlog save aging (expire\u logs\u days)
- (4) Pyqt5 tutorial -- > Custom signal and slot (super winding...)
- Renrenyue -- renrenyue system development source code sharing
- Hook technology
- The first techo day Tencent technology open day in 2022 will be held online on June 28
- GPS NMEA protocol, 0183 positioning data format dual mode positioning: gnxxx gps+bd full version
- Ten commandments of self-learning in machine learning
- Zhangxiaobai's way of penetration (V) -- detailed explanation of upload vulnerability and parsing vulnerability
- Total number of MySQL statistics, used and unused
- Micro engine remote attachment 7 Niu cloud upload
猜你喜欢

Go from 0 to 1. Obtain the installation package, get, post request, params, body and other parameters

The server reported an error 503 service unavailable:the system returned: (71) protocol error

An article clearly explains MySQL's clustering / Federation / coverage index, back to table, and index push down

三入职场!你可以从我身上学到这些(附毕业Vlog)

Swagger document generated by node project API in vscode

(2) Pyqt5 tutorial -- > using qtdesigner to separate interface code

(3) Pyqt5 tutorial -- > signal and slot preliminary test

High performance + million level Excel data import and export

ECSHOP commodity wholesale multi attribute multi specification multi inventory batch purchase ECSHOP wholesale plug-in ECSHOP multi attribute order

Service charge and time setting code sharing involved in crmeb withdrawal process
随机推荐
(3) Pyqt5 tutorial -- > signal and slot preliminary test
JS array length is defined
mysql FIND_ IN_ Set function
QT TCP UDP network communication < theory >
ECSHOP quickly purchases goods, simplifies the shopping process, and improves the user experience through one-step shopping
Fun pocket mall -- sharing the development source code of fun pocket app system
JS enter three integers a, B and C, and sort them from large to small (two methods)
Laravel task scheduling
When MySQL queries fields in JSON format, it takes a property value of JSON data
The first techo day Tencent technology open day in 2022 will be held online on June 28
Go defer little knowledge
R language dplyr package filter function filters the data rows in the specified list whose contents in the dataframe data are not (not equal to one of the specified vectors)
C program linking SQLSERVER database: instance failed
Introduction to jiuhongtianxia system development function -- jiuhongtianxia app development source code sharing
ThinkPHP upload image compression size
Zunpin Yongyao advertising e-commerce system -- Zunpin Yongyao advertising e-commerce app system development source code sharing
Figure explanation of fiborache sequence
Polling and long polling
Zhangxiaobai's road to penetration (7) -sql injection detailed operation steps -union joint query injection
Digital currency exchange -- digital currency exchange system development source code sharing