| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | //  ThreadPool.cpp
 | 
					
						
							|  |  |  | //  MNN
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //  Created by MNN on 2019/06/30.
 | 
					
						
							|  |  |  | //  Copyright © 2018, Alibaba Group Holding Limited
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | #ifdef MNN_USE_THREAD_POOL
 | 
					
						
							| 
									
										
										
										
											2019-12-27 22:16:57 +08:00
										 |  |  | #include "backend/cpu/ThreadPool.hpp"
 | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  | #include <string.h>
 | 
					
						
							| 
									
										
										
										
											2019-12-27 22:16:57 +08:00
										 |  |  | #include <MNN/MNNDefine.h>
 | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  | #ifdef __ANDROID__
 | 
					
						
							|  |  |  | #include <stdint.h>
 | 
					
						
							|  |  |  | #include <sys/syscall.h>
 | 
					
						
							|  |  |  | #include <unistd.h>
 | 
					
						
							|  |  |  | #include <algorithm>
 | 
					
						
							|  |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  | //#define MNN_THREAD_LOCK_CPU
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define MNN_THREAD_POOL_MAX_TASKS 2
 | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  | namespace MNN { | 
					
						
							|  |  |  | ThreadPool* ThreadPool::gInstance = nullptr; | 
					
						
							|  |  |  | static std::mutex gInitMutex; | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  | int ThreadPool::init(int number) { | 
					
						
							|  |  |  |     if (1 >= number) { | 
					
						
							|  |  |  |         return 1; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-07-30 17:12:52 +08:00
										 |  |  |     std::lock_guard<std::mutex> _l(gInitMutex); | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |     if (nullptr != gInstance) { | 
					
						
							|  |  |  |         if (gInstance->number() < number) { | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |             return gInstance->number(); | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (nullptr == gInstance) { | 
					
						
							|  |  |  |         gInstance = new ThreadPool(number); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |     return number; | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  | } | 
					
						
							|  |  |  | void ThreadPool::destroy() { | 
					
						
							| 
									
										
										
										
											2019-07-30 17:12:52 +08:00
										 |  |  |     std::lock_guard<std::mutex> _l(gInitMutex); | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |     if (nullptr != gInstance) { | 
					
						
							|  |  |  |         delete gInstance; | 
					
						
							|  |  |  |         gInstance = nullptr; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  | #ifdef MNN_THREAD_LOCK_CPU
 | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  | static int getNumberOfCPU() { | 
					
						
							|  |  |  |     FILE* fp = fopen("/proc/cpuinfo", "rb"); | 
					
						
							|  |  |  |     if (!fp) { | 
					
						
							|  |  |  |         return 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     int number = 0; | 
					
						
							|  |  |  |     char buffer[1024]; | 
					
						
							|  |  |  |     while (!feof(fp)) { | 
					
						
							|  |  |  |         char* str = fgets(buffer, 1024, fp); | 
					
						
							|  |  |  |         if (!str) { | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (memcmp(buffer, "processor", 9) == 0) { | 
					
						
							|  |  |  |             number++; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     fclose(fp); | 
					
						
							|  |  |  |     if (number < 1) { | 
					
						
							|  |  |  |         number = 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return number; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int getCPUMaxFreqKHz(int cpuID) { | 
					
						
							|  |  |  |     char path[256]; | 
					
						
							|  |  |  |     sprintf(path, "/sys/devices/system/cpu/cpufreq/stats/cpu%d/time_in_state", cpuID); | 
					
						
							|  |  |  |     FILE* fp = fopen(path, "rb"); | 
					
						
							|  |  |  |     if (!fp) { | 
					
						
							|  |  |  |         sprintf(path, "/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state", cpuID); | 
					
						
							|  |  |  |         fp = fopen(path, "rb"); | 
					
						
							|  |  |  |         if (!fp) { | 
					
						
							|  |  |  |             sprintf(path, "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", cpuID); | 
					
						
							|  |  |  |             fp = fopen(path, "rb"); | 
					
						
							|  |  |  |             if (!fp) { | 
					
						
							|  |  |  |                 return -1; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             int maxfrequency = -1; | 
					
						
							|  |  |  |             fscanf(fp, "%d", &maxfrequency); | 
					
						
							|  |  |  |             fclose(fp); | 
					
						
							|  |  |  |             return maxfrequency; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     int maxfrequency = 0; | 
					
						
							|  |  |  |     while (!feof(fp)) { | 
					
						
							|  |  |  |         int frequency = 0; | 
					
						
							|  |  |  |         int history   = fscanf(fp, "%d %*d", &frequency); | 
					
						
							|  |  |  |         if (history != 1) { | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (frequency > maxfrequency) { | 
					
						
							|  |  |  |             maxfrequency = frequency; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     fclose(fp); | 
					
						
							|  |  |  |     return maxfrequency; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static std::vector<int> sortCPUIDByMaxFrequency(int maxNumbers) { | 
					
						
							|  |  |  |     const int cpuNumbers = getNumberOfCPU(); | 
					
						
							|  |  |  |     if (cpuNumbers == 0) { | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     std::vector<int> cpuIDs; | 
					
						
							|  |  |  |     std::vector<std::pair<int, int>> cpusFrequency; | 
					
						
							|  |  |  |     cpusFrequency.resize(cpuNumbers); | 
					
						
							|  |  |  |     for (int i = 0; i < cpuNumbers; ++i) { | 
					
						
							|  |  |  |         int frequency           = getCPUMaxFreqKHz(i); | 
					
						
							|  |  |  |         cpusFrequency[i].first  = frequency; | 
					
						
							|  |  |  |         cpusFrequency[i].second = i; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     maxNumbers = std::min(maxNumbers, cpuNumbers); | 
					
						
							|  |  |  |     std::sort(cpusFrequency.rbegin(), cpusFrequency.rend()); | 
					
						
							|  |  |  |     cpuIDs.resize(maxNumbers); | 
					
						
							|  |  |  |     for (int i = 0; i < maxNumbers; ++i) { | 
					
						
							|  |  |  |         cpuIDs[i] = cpusFrequency[i].second; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |     // FUNC_PRINT(cpusFrequency[0].first);
 | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |     return cpuIDs; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int setSchedAffinity(const std::vector<int>& cpuIDs) { | 
					
						
							|  |  |  | #define __NCPUBITS (8 * sizeof(unsigned long))
 | 
					
						
							|  |  |  |     typedef struct { | 
					
						
							|  |  |  |         unsigned long __bits[CPU_SETSIZE / __NCPUBITS]; | 
					
						
							|  |  |  |     } cpu_set_t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // set affinity for thread
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     pid_t pid = gettid(); | 
					
						
							|  |  |  |     cpu_set_t mask; | 
					
						
							|  |  |  |     CPU_ZERO(&mask); | 
					
						
							| 
									
										
										
										
											2019-12-30 15:56:55 +08:00
										 |  |  |     for (int i = 1; i < (int)cpuIDs.size(); i++) { | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |         CPU_SET(cpuIDs[i], &mask); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     int syscallret = syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask); | 
					
						
							|  |  |  |     if (syscallret) { | 
					
						
							|  |  |  |         MNN_PRINT("syscall error %d\n", syscallret); | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #endif // arch
 | 
					
						
							|  |  |  | ThreadPool::ThreadPool(int numberThread) { | 
					
						
							|  |  |  |     mNumberThread = numberThread; | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |     mActiveCount  = 0; | 
					
						
							|  |  |  |     mTaskAvailable.resize(MNN_THREAD_POOL_MAX_TASKS); | 
					
						
							|  |  |  |     mTasks.resize(MNN_THREAD_POOL_MAX_TASKS); | 
					
						
							|  |  |  |     for (int t = 0; t < mTasks.size(); ++t) { | 
					
						
							|  |  |  |         mTaskAvailable[t] = true; | 
					
						
							|  |  |  |         for (int i = 0; i < mNumberThread; ++i) { | 
					
						
							|  |  |  |             mTasks[t].second.emplace_back(new std::atomic_bool{false}); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  | #ifdef MNN_THREAD_LOCK_CPU
 | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |     std::vector<int> sortedCPUIDs = sortCPUIDByMaxFrequency(numberThread); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |     for (int i = 1; i < mNumberThread; ++i) { | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |         int threadIndex = i; | 
					
						
							|  |  |  | #ifdef MNN_THREAD_LOCK_CPU
 | 
					
						
							|  |  |  |         mWorkers.emplace_back([this, sortedCPUIDs, threadIndex]() { | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  | #else
 | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |         mWorkers.emplace_back([this, threadIndex]() { | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  | #ifdef MNN_THREAD_LOCK_CPU
 | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |             int res = setSchedAffinity(sortedCPUIDs); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |             while (!mStop) { | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |                 while (mActiveCount > 0) { | 
					
						
							|  |  |  |                     for (int i = 0; i < MNN_THREAD_POOL_MAX_TASKS; ++i) { | 
					
						
							|  |  |  |                         if (*mTasks[i].second[threadIndex]) { | 
					
						
							|  |  |  |                             mTasks[i].first.first(threadIndex); | 
					
						
							|  |  |  |                             { *mTasks[i].second[threadIndex] = false; } | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     std::this_thread::yield(); | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |                 std::unique_lock<std::mutex> _l(mQueueMutex); | 
					
						
							|  |  |  |                 mCondition.wait(_l, [this] { return mStop || mActiveCount > 0; }); | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ThreadPool::~ThreadPool() { | 
					
						
							| 
									
										
										
										
											2019-07-25 13:36:35 +08:00
										 |  |  |     mStop = true; | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |     mCondition.notify_all(); | 
					
						
							|  |  |  |     for (auto& worker : mWorkers) { | 
					
						
							|  |  |  |         worker.join(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |     for (auto& task : mTasks) { | 
					
						
							|  |  |  |         for (auto c : task.second) { | 
					
						
							|  |  |  |             delete c; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  | int ThreadPool::acquireWorkIndex() { | 
					
						
							|  |  |  |     if (nullptr == gInstance) { | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-07-30 17:12:52 +08:00
										 |  |  |     std::lock_guard<std::mutex> _l(gInstance->mQueueMutex); | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |     for (int i = 0; i < MNN_THREAD_POOL_MAX_TASKS; ++i) { | 
					
						
							|  |  |  |         if (gInstance->mTaskAvailable[i]) { | 
					
						
							|  |  |  |             gInstance->mTaskAvailable[i] = false; | 
					
						
							|  |  |  |             return i; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return -1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | void ThreadPool::releaseWorkIndex(int index) { | 
					
						
							|  |  |  |     if (nullptr == gInstance) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (index < 0 || index >= MNN_THREAD_POOL_MAX_TASKS) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-07-30 17:12:52 +08:00
										 |  |  |     std::lock_guard<std::mutex> _l(gInstance->mQueueMutex); | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |     gInstance->mTaskAvailable[index] = true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void ThreadPool::active() { | 
					
						
							|  |  |  |     if (nullptr == gInstance) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     gInstance->mActiveCount++; | 
					
						
							| 
									
										
										
										
											2019-08-07 16:44:09 +08:00
										 |  |  |     std::lock_guard<std::mutex> _l(gInstance->mQueueMutex); | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |     gInstance->mCondition.notify_all(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | void ThreadPool::deactive() { | 
					
						
							|  |  |  |     if (nullptr == gInstance) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     gInstance->mActiveCount--; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void ThreadPool::enqueue(TASK&& task, int index) { | 
					
						
							|  |  |  |     if (1 >= task.second || 0 > index) { | 
					
						
							|  |  |  |         for (int i = 0; i < task.second; ++i) { | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |             task.first(i); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |     MNN_ASSERT(nullptr != gInstance); | 
					
						
							|  |  |  |     gInstance->enqueueInternal(std::move(task), index); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | void ThreadPool::enqueueInternal(TASK&& task, int index) { | 
					
						
							| 
									
										
											  
											
												- build:
	- unify schema building in core and converter;
	- add more build script for android;
	- add linux build script for python;
- ops impl:
	- add floor mod support in binary;
	- use eltwise impl in add/max/sub/mul binary for optimization;
	- remove fake double support in cast;
	- fix 5d support for concat;
	- add adjX and adjY support for batch matmul;
	- optimize conv2d back prop filter;
	- add pad mode support for conv3d;
	- fix bug in conv2d & conv depthwise with very small feature map;
	- optimize binary without broacast;
	- add data types support for gather;
	- add gather ND support;
	- use uint8 data type in gather v2;
	- add transpose support for matmul;
	- add matrix band part;
	- add dim != 4 support for padding, reshape & tensor convert;
	- add pad type support for pool3d;
	- make ops based on TensorFlow Lite quantization optional;
	- add all & any support for reduction;
	- use type in parameter as output type in reduction;
	- add int support for unary;
	- add variable weight support for conv2d;
	- fix conv2d depthwise weights initialization;
	- fix type support for transpose;
	- fix grad outputs count for  reduce grad and reshape grad;
	- fix priorbox & detection output;
	- fix metal softmax error;
- python:
	- add runSessionWithCallBackInfo interface;
	- add max nodes limit (1400) for visualization tool;
	- fix save error in python3;
	- align default dim;
- convert:
	- add extra design for optimization;
	- add more post converting optimizers;
	- add caffe v1 weights blob support;
	- add cast, unary, conv transpose support for onnx model;
	- optimize batchnorm, conv with variable weights, prelu, reshape, slice, upsample for onnx model;
	- add cos/sin/atan/tan support for unary for tensorflow model;
	- add any/all support for reduction for tensorflow model;
	- add elu, conv3d, pool3d support for tensorflow model;
	- optimize argmax, batchnorm, concat, batch to space, conv with variable weights, prelu, slice for tensorflow model;
- others:
	- fix size computer lock;
	- fix thread pool deadlock;
	- add express & parameters in express;
	- rewrite blitter chooser without static map;
	- add tests for expr;
											
										 
											2019-10-29 13:37:26 +08:00
										 |  |  |     if (mActiveCount == 0) { | 
					
						
							|  |  |  |         for (int i = 0; i < task.second; ++i) { | 
					
						
							|  |  |  |             task.first(i); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |     int workSize = task.second; | 
					
						
							|  |  |  |     if (workSize > mNumberThread) { | 
					
						
							|  |  |  |         mTasks[index].first = std::make_pair( | 
					
						
							|  |  |  |             [workSize, &task, this](int tId) { | 
					
						
							|  |  |  |                 for (int v = tId; v < workSize; v += mNumberThread) { | 
					
						
							|  |  |  |                     task.first(v); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             mNumberThread); | 
					
						
							|  |  |  |         workSize = mNumberThread; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         mTasks[index].first = std::move(task); | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |     } | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |         for (int i = 1; i < workSize; ++i) { | 
					
						
							|  |  |  |             *mTasks[index].second[i] = true; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-07-19 17:09:09 +08:00
										 |  |  |     mTasks[index].first.first(0); | 
					
						
							|  |  |  |     bool complete = true; | 
					
						
							|  |  |  |     do { | 
					
						
							|  |  |  |         std::this_thread::yield(); | 
					
						
							|  |  |  |         complete = true; | 
					
						
							|  |  |  |         for (int i = 1; i < workSize; ++i) { | 
					
						
							|  |  |  |             if (*mTasks[index].second[i]) { | 
					
						
							|  |  |  |                 complete = false; | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         // FUNC_PRINT(notComplete);
 | 
					
						
							|  |  |  |     } while (!complete); | 
					
						
							| 
									
										
										
										
											2019-07-11 13:56:52 +08:00
										 |  |  | } | 
					
						
							|  |  |  | } // namespace MNN
 | 
					
						
							|  |  |  | #endif
 |