当前位置:网站首页>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 .
边栏推荐
- Summary of common MySQL database commands (from my own view)
- 2022 meisai D topic ideas sharing + translation
- Does sklearex make your sklearn machine learning model training fly fast?
- How can we differ LEFT OUTER JOIN vs Left Join [duplicate]
- Service charge and time setting code sharing involved in crmeb withdrawal process
- visual studio2019链接opencv
- Qt5 multi thread operation implemented by object base class and use of movetothread method
- 2022 meisai topic C idea sharing + translation
- Initialize the project using the express framework
- Windows下MySQL的安装和删除
猜你喜欢

(7) Pyqt5 tutorial -- > > window properties and basic controls (continuous update)

mysql FIND_ IN_ Set function
![最大数[抽象排序之抽象规则]](/img/47/f6bafacc95f487854a3e304325f3f0.png)
最大数[抽象排序之抽象规则]

Happy shopkeeper source code -- Introduction to happy shopkeeper system development mode

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

QT TCP UDP network communication < theory >

2022 meisai topic C idea sharing + translation

Zhangxiaobai's road of penetration (VI) -- the idea and process of SQL injection and the concat series functions and information of SQL_ Schema database explanation

(6) Pyqt5--- > window jump (registration login function)

Windows下MySQL的安装和删除
随机推荐
Shell learning notes (latest update: 2022-02-18)
Renrenyue -- renrenyue system development source code sharing
浏览器的5种观察器
Node child processes and threads
最大数[抽象排序之抽象规则]
初识CANOpen
Why do we do autocorrelation analysis? Explain application scenarios and specific operations
百度搜索稳定性问题分析的故事
PHP takes the difference set of two arrays
MySQL and excel tables importing database data (Excel for MySQL)
Laravel echart statistical chart line chart
ECSHOP video list_ ECSHOP uploading video, video classification, video list playing video function
C program linking SQLSERVER database: instance failed
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)
Penetration tool environment -- use of cknife Chinese kitchen knife
The difference between this and super and their respective functions
Is it safe to open an account and buy stocks on the Internet?
Dynamic proxy
The R language cartools package divides data, the scale function scales data, and the NaiveBayes function of e1071 package constructs a naive Bayesian model
JS enter three integers a, B and C, and sort them from large to small (two methods)