core/Prefetcher.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "Prefetcher.h" | ||
| 2 | |||
| 3 | #include <vector> | ||
| 4 | #include <mutex> | ||
| 5 | #include <atomic> | ||
| 6 | #ifdef INTEL_INTRINSICS | ||
| 7 | #include <mmintrin.h> | ||
| 8 | #endif | ||
| 9 | #include <avisynth.h> | ||
| 10 | #include "ThreadPool.h" | ||
| 11 | #include "ObjectPool.h" | ||
| 12 | #include "LruCache.h" | ||
| 13 | #include "InternalEnvironment.h" | ||
| 14 | #include "internal.h" | ||
| 15 | |||
| 16 | struct PrefetcherJobParams | ||
| 17 | { | ||
| 18 | int frame; | ||
| 19 | Prefetcher* prefetcher; | ||
| 20 | LruCache<size_t, PVideoFrame>::handle cache_handle; | ||
| 21 | }; | ||
| 22 | |||
| 23 | struct PrefetcherPimpl | ||
| 24 | { | ||
| 25 | PClip child; | ||
| 26 | VideoInfo vi; | ||
| 27 | |||
| 28 | // The number of threads to use for prefetching | ||
| 29 | const int nThreads; | ||
| 30 | |||
| 31 | // Maximum number of frames to prefetch | ||
| 32 | const int nPrefetchFrames; | ||
| 33 | |||
| 34 | ThreadPool* thread_pool; | ||
| 35 | |||
| 36 | ObjectPool<PrefetcherJobParams> JobParamsPool; | ||
| 37 | std::mutex params_pool_mutex; | ||
| 38 | |||
| 39 | // Contains the pattern we are locked on to | ||
| 40 | int LockedPattern; | ||
| 41 | |||
| 42 | // The number of consecutive frames Pattern has repeated itself | ||
| 43 | int PatternHits; | ||
| 44 | |||
| 45 | // The number of consecutive frames LockedPattern was invalid | ||
| 46 | int PatternMisses; | ||
| 47 | |||
| 48 | // The current pattern that we are not locked on to | ||
| 49 | int Pattern; | ||
| 50 | |||
| 51 | // True if we have found a pattern to lock onto | ||
| 52 | bool IsLocked; | ||
| 53 | |||
| 54 | // The frame number that GetFrame() has been called with the last time | ||
| 55 | int LastRequestedFrame; | ||
| 56 | |||
| 57 | std::shared_ptr<LruCache<size_t, PVideoFrame> > VideoCache; | ||
| 58 | std::atomic<int> running_workers; | ||
| 59 | std::mutex worker_exception_mutex; | ||
| 60 | std::exception_ptr worker_exception; | ||
| 61 | bool worker_exception_present; | ||
| 62 | InternalEnvironment *EnvI; | ||
| 63 | |||
| 64 | ✗ | PrefetcherPimpl(const PClip& _child, int _nThreads, int _nPrefetchFrames, IScriptEnvironment2 *env2) : | |
| 65 | ✗ | child(_child), | |
| 66 | ✗ | vi(_child->GetVideoInfo()), | |
| 67 | ✗ | nThreads(_nThreads), | |
| 68 | ✗ | nPrefetchFrames(_nPrefetchFrames), | |
| 69 | ✗ | thread_pool(NULL), | |
| 70 | ✗ | LockedPattern(1), | |
| 71 | ✗ | PatternHits(0), | |
| 72 | ✗ | PatternMisses(0), | |
| 73 | ✗ | Pattern(1), | |
| 74 | ✗ | IsLocked(false), | |
| 75 | ✗ | LastRequestedFrame(0), | |
| 76 | ✗ | VideoCache(NULL), | |
| 77 | ✗ | running_workers(0), | |
| 78 | ✗ | worker_exception_present(0), | |
| 79 | ✗ | EnvI(static_cast<InternalEnvironment*>(env2)) | |
| 80 | { | ||
| 81 | ✗ | thread_pool = EnvI->NewThreadPool(nThreads); | |
| 82 | ✗ | } | |
| 83 | |||
| 84 | ✗ | ~PrefetcherPimpl() | |
| 85 | { | ||
| 86 | ✗ | for (void* data : thread_pool->Finish()) { | |
| 87 | ✗ | PrefetcherJobParams *ptr = (PrefetcherJobParams*)data; | |
| 88 | ✗ | VideoCache->rollback(&ptr->cache_handle); | |
| 89 | } | ||
| 90 | ✗ | } | |
| 91 | }; | ||
| 92 | |||
| 93 | |||
| 94 | // The number of intervals a pattern has to repeat itself to become (un)locked | ||
| 95 | #define PATTERN_LOCK_LENGTH 3 | ||
| 96 | |||
| 97 | ✗ | AVSValue Prefetcher::ThreadWorker(IScriptEnvironment2* env, void* data) | |
| 98 | { | ||
| 99 | ✗ | PrefetcherJobParams *ptr = (PrefetcherJobParams*)data; | |
| 100 | ✗ | Prefetcher *prefetcher = ptr->prefetcher; | |
| 101 | ✗ | int n = ptr->frame; | |
| 102 | ✗ | LruCache<size_t, PVideoFrame>::handle cache_handle = ptr->cache_handle; | |
| 103 | |||
| 104 | { | ||
| 105 | ✗ | std::lock_guard<std::mutex> lock(prefetcher->_pimpl->params_pool_mutex); | |
| 106 | ✗ | prefetcher->_pimpl->JobParamsPool.Destruct(ptr); | |
| 107 | ✗ | } | |
| 108 | |||
| 109 | try | ||
| 110 | { | ||
| 111 | ✗ | cache_handle.first->value = prefetcher->_pimpl->child->GetFrame(n, env); | |
| 112 | #ifdef INTEL_INTRINSICS | ||
| 113 | #ifdef X86_32 | ||
| 114 | _mm_empty(); | ||
| 115 | #endif | ||
| 116 | #endif | ||
| 117 | |||
| 118 | ✗ | prefetcher->_pimpl->VideoCache->commit_value(&cache_handle); | |
| 119 | ✗ | --(prefetcher->_pimpl->running_workers); | |
| 120 | } | ||
| 121 | ✗ | catch(...) | |
| 122 | { | ||
| 123 | ✗ | prefetcher->_pimpl->VideoCache->rollback(&cache_handle); | |
| 124 | |||
| 125 | ✗ | std::lock_guard<std::mutex> lock(prefetcher->_pimpl->worker_exception_mutex); | |
| 126 | ✗ | prefetcher->_pimpl->worker_exception = std::current_exception(); | |
| 127 | ✗ | prefetcher->_pimpl->worker_exception_present = true; | |
| 128 | ✗ | --(prefetcher->_pimpl->running_workers); | |
| 129 | ✗ | } | |
| 130 | |||
| 131 | ✗ | return AVSValue(); | |
| 132 | ✗ | } | |
| 133 | |||
| 134 | ✗ | Prefetcher::Prefetcher(const PClip& _child, int _nThreads, int _nPrefetchFrames, IScriptEnvironment *env) : | |
| 135 | ✗ | _pimpl(NULL) | |
| 136 | { | ||
| 137 | ✗ | _pimpl = new PrefetcherPimpl(_child, _nThreads, _nPrefetchFrames, static_cast<IScriptEnvironment2*>(env)); | |
| 138 | ✗ | _pimpl->VideoCache = std::make_shared<LruCache<size_t, PVideoFrame> >(_pimpl->nPrefetchFrames*2, CACHE_NO_RESIZE); | |
| 139 | ✗ | } | |
| 140 | |||
| 141 | ✗ | void Prefetcher::Destroy() | |
| 142 | { | ||
| 143 | ✗ | if (_pimpl) | |
| 144 | { | ||
| 145 | ✗ | PrefetcherPimpl *pimpl = _pimpl; | |
| 146 | ✗ | delete pimpl; | |
| 147 | ✗ | _pimpl = nullptr; | |
| 148 | } | ||
| 149 | ✗ | } | |
| 150 | |||
| 151 | ✗ | Prefetcher::~Prefetcher() | |
| 152 | { | ||
| 153 | ✗ | Destroy(); | |
| 154 | ✗ | } | |
| 155 | |||
| 156 | ✗ | size_t Prefetcher::NumPrefetchThreads() const | |
| 157 | { | ||
| 158 | ✗ | return _pimpl->nThreads; | |
| 159 | } | ||
| 160 | |||
| 161 | ✗ | int __stdcall Prefetcher::SchedulePrefetch(int current_n, int prefetch_start, InternalEnvironment* env) | |
| 162 | { | ||
| 163 | ✗ | int n = prefetch_start; | |
| 164 | ✗ | while ((_pimpl->running_workers < _pimpl->nPrefetchFrames) && (std::abs(n - current_n) < _pimpl->nPrefetchFrames) ) | |
| 165 | { | ||
| 166 | ✗ | n += _pimpl->IsLocked ? _pimpl->LockedPattern : 1; | |
| 167 | ✗ | if (n >= _pimpl->vi.num_frames) | |
| 168 | ✗ | break; | |
| 169 | |||
| 170 | ✗ | PVideoFrame result; | |
| 171 | ✗ | LruCache<size_t, PVideoFrame>::handle cache_handle; | |
| 172 | ✗ | switch(_pimpl->VideoCache->lookup(n, &cache_handle, false, result)) | |
| 173 | { | ||
| 174 | ✗ | case LRU_LOOKUP_NOT_FOUND: | |
| 175 | { | ||
| 176 | ✗ | PrefetcherJobParams *p = NULL; | |
| 177 | { | ||
| 178 | ✗ | std::lock_guard<std::mutex> lock(_pimpl->params_pool_mutex); | |
| 179 | ✗ | p = _pimpl->JobParamsPool.Construct(); | |
| 180 | ✗ | } | |
| 181 | ✗ | p->frame = n; | |
| 182 | ✗ | p->prefetcher = this; | |
| 183 | ✗ | p->cache_handle = cache_handle; | |
| 184 | ✗ | ++_pimpl->running_workers; | |
| 185 | ✗ | _pimpl->thread_pool->QueueJob(ThreadWorker, p, env, NULL); | |
| 186 | ✗ | break; | |
| 187 | } | ||
| 188 | ✗ | case LRU_LOOKUP_FOUND_AND_READY: // Fall-through intentional | |
| 189 | case LRU_LOOKUP_NO_CACHE: // Fall-through intentional | ||
| 190 | case LRU_LOOKUP_FOUND_BUT_NOTAVAIL: | ||
| 191 | { | ||
| 192 | ✗ | break; | |
| 193 | } | ||
| 194 | ✗ | default: | |
| 195 | { | ||
| 196 | ✗ | assert(0); | |
| 197 | break; | ||
| 198 | } | ||
| 199 | } | ||
| 200 | ✗ | } // switch | |
| 201 | |||
| 202 | ✗ | return n; | |
| 203 | } | ||
| 204 | |||
| 205 | ✗ | PVideoFrame __stdcall Prefetcher::GetFrame(int n, IScriptEnvironment* env_) | |
| 206 | { | ||
| 207 | ✗ | InternalEnvironment* IEnv = GetAndRevealCamouflagedEnv(env_); | |
| 208 | ✗ | IScriptEnvironment* env = static_cast<IScriptEnvironment*>(IEnv); | |
| 209 | |||
| 210 | ✗ | if (IEnv->GetSuppressThreadCount() > 0) { | |
| 211 | // do not use thread when invoke running | ||
| 212 | ✗ | return _pimpl->child->GetFrame(n, env); | |
| 213 | } | ||
| 214 | |||
| 215 | ✗ | int pattern = n - _pimpl->LastRequestedFrame; | |
| 216 | ✗ | _pimpl->LastRequestedFrame = n; | |
| 217 | ✗ | if (pattern == 0) | |
| 218 | ✗ | pattern = 1; | |
| 219 | |||
| 220 | ✗ | if (_pimpl->IsLocked) | |
| 221 | { | ||
| 222 | ✗ | if (_pimpl->LockedPattern == pattern) | |
| 223 | { | ||
| 224 | ✗ | _pimpl->PatternHits = 0; // Tracks Pattern | |
| 225 | ✗ | _pimpl->PatternMisses = 0; // Tracks LockedPattern | |
| 226 | } | ||
| 227 | ✗ | else if (_pimpl->Pattern == pattern) | |
| 228 | { | ||
| 229 | ✗ | _pimpl->PatternHits++; // Tracks Pattern | |
| 230 | ✗ | _pimpl->PatternMisses++; // Tracks LockedPattern | |
| 231 | } | ||
| 232 | else | ||
| 233 | { | ||
| 234 | ✗ | _pimpl->PatternHits = 0; // Tracks Pattern | |
| 235 | ✗ | _pimpl->PatternMisses++; // Tracks LockedPattern | |
| 236 | } | ||
| 237 | ✗ | _pimpl->Pattern = pattern; | |
| 238 | |||
| 239 | ✗ | if ((_pimpl->PatternMisses >= PATTERN_LOCK_LENGTH) && (_pimpl->PatternHits >= PATTERN_LOCK_LENGTH)) | |
| 240 | { | ||
| 241 | ✗ | _pimpl->LockedPattern = _pimpl->Pattern; | |
| 242 | ✗ | _pimpl->PatternHits = 0; // Tracks Pattern | |
| 243 | ✗ | _pimpl->PatternMisses = 0; // Tracks LockedPattern | |
| 244 | } | ||
| 245 | ✗ | else if ((_pimpl->PatternMisses >= PATTERN_LOCK_LENGTH) && (_pimpl->PatternHits < PATTERN_LOCK_LENGTH)) | |
| 246 | { | ||
| 247 | ✗ | _pimpl->IsLocked = false; | |
| 248 | } | ||
| 249 | } | ||
| 250 | else | ||
| 251 | { | ||
| 252 | ✗ | if (_pimpl->Pattern == pattern) | |
| 253 | { | ||
| 254 | ✗ | _pimpl->PatternHits++; // Tracks Pattern | |
| 255 | ✗ | _pimpl->PatternMisses = 0; // Tracks Pattern | |
| 256 | } | ||
| 257 | else | ||
| 258 | { | ||
| 259 | ✗ | _pimpl->PatternHits = 0; // Tracks Pattern | |
| 260 | ✗ | _pimpl->PatternMisses++; // Tracks Pattern | |
| 261 | } | ||
| 262 | |||
| 263 | ✗ | if (_pimpl->PatternHits >= PATTERN_LOCK_LENGTH) | |
| 264 | { | ||
| 265 | ✗ | _pimpl->LockedPattern = pattern; | |
| 266 | ✗ | _pimpl->PatternMisses = 0; // Tracks Pattern | |
| 267 | ✗ | _pimpl->IsLocked = true; | |
| 268 | } | ||
| 269 | } | ||
| 270 | |||
| 271 | |||
| 272 | { | ||
| 273 | ✗ | std::lock_guard<std::mutex> lock(_pimpl->worker_exception_mutex); | |
| 274 | ✗ | if (_pimpl->worker_exception_present) | |
| 275 | { | ||
| 276 | ✗ | std::rethrow_exception(_pimpl->worker_exception); | |
| 277 | } | ||
| 278 | ✗ | } | |
| 279 | |||
| 280 | |||
| 281 | // Prefetch 1 | ||
| 282 | ✗ | int prefetch_pos = SchedulePrefetch(n, n, IEnv); | |
| 283 | |||
| 284 | // Get requested frame | ||
| 285 | ✗ | PVideoFrame result; | |
| 286 | ✗ | LruCache<size_t, PVideoFrame>::handle cache_handle; | |
| 287 | // fill result if LRU_LOOKUP_FOUND_AND_READY | ||
| 288 | ✗ | switch(_pimpl->VideoCache->lookup(n, &cache_handle, true, result)) | |
| 289 | { | ||
| 290 | ✗ | case LRU_LOOKUP_NOT_FOUND: | |
| 291 | { | ||
| 292 | try | ||
| 293 | { | ||
| 294 | ✗ | result = _pimpl->child->GetFrame(n, env); // P.F. fill result before Commit! | |
| 295 | ✗ | cache_handle.first->value = result; | |
| 296 | // cache_handle.first->value = _pimpl->child->GetFrame(n, env); // P.F. before Commit! | ||
| 297 | #ifdef INTEL_INTRINSICS | ||
| 298 | #ifdef X86_32 | ||
| 299 | _mm_empty(); | ||
| 300 | #endif | ||
| 301 | #endif | ||
| 302 | ✗ | _pimpl->VideoCache->commit_value(&cache_handle); | |
| 303 | } | ||
| 304 | ✗ | catch(...) | |
| 305 | { | ||
| 306 | ✗ | _pimpl->VideoCache->rollback(&cache_handle); | |
| 307 | ✗ | throw; | |
| 308 | ✗ | } | |
| 309 | ✗ | break; | |
| 310 | } | ||
| 311 | ✗ | case LRU_LOOKUP_FOUND_AND_READY: | |
| 312 | { | ||
| 313 | //result = cache_handle.first->value; // old method, result is filled already | ||
| 314 | ✗ | break; | |
| 315 | } | ||
| 316 | ✗ | case LRU_LOOKUP_NO_CACHE: | |
| 317 | { | ||
| 318 | ✗ | result = _pimpl->child->GetFrame(n, env); | |
| 319 | ✗ | break; | |
| 320 | } | ||
| 321 | ✗ | case LRU_LOOKUP_FOUND_BUT_NOTAVAIL: // Fall-through intentional | |
| 322 | default: | ||
| 323 | { | ||
| 324 | ✗ | assert(0); | |
| 325 | break; | ||
| 326 | } | ||
| 327 | } | ||
| 328 | |||
| 329 | // Prefetch 2 | ||
| 330 | ✗ | SchedulePrefetch(n, prefetch_pos, IEnv); | |
| 331 | |||
| 332 | ✗ | return result; | |
| 333 | ✗ | } | |
| 334 | |||
| 335 | ✗ | bool __stdcall Prefetcher::GetParity(int n) | |
| 336 | { | ||
| 337 | ✗ | return _pimpl->child->GetParity(n); | |
| 338 | } | ||
| 339 | |||
| 340 | ✗ | void __stdcall Prefetcher::GetAudio(void* buf, int64_t start, int64_t count, IScriptEnvironment* env_) | |
| 341 | { | ||
| 342 | ✗ | InternalEnvironment* IEnv = GetAndRevealCamouflagedEnv(env_); | |
| 343 | ✗ | IScriptEnvironment* env = static_cast<IScriptEnvironment*>(IEnv); | |
| 344 | |||
| 345 | ✗ | _pimpl->child->GetAudio(buf, start, count, env); | |
| 346 | ✗ | } | |
| 347 | |||
| 348 | ✗ | int __stdcall Prefetcher::SetCacheHints(int cachehints, int frame_range) | |
| 349 | { | ||
| 350 | AVS_UNUSED(frame_range); | ||
| 351 | ✗ | if (CACHE_GET_MTMODE == cachehints) | |
| 352 | ✗ | return MT_NICE_FILTER; | |
| 353 | |||
| 354 | ✗ | if (CACHE_GET_DEV_TYPE == cachehints) | |
| 355 | ✗ | return (_pimpl->child->GetVersion() >= 5) ? _pimpl->child->SetCacheHints(CACHE_GET_DEV_TYPE, 0) : 0; | |
| 356 | |||
| 357 | ✗ | return 0; | |
| 358 | } | ||
| 359 | |||
| 360 | ✗ | const VideoInfo& __stdcall Prefetcher::GetVideoInfo() | |
| 361 | { | ||
| 362 | ✗ | return _pimpl->vi; | |
| 363 | } | ||
| 364 | |||
| 365 | ✗ | AVSValue Prefetcher::Create(AVSValue args, void*, IScriptEnvironment* env) | |
| 366 | { | ||
| 367 | ✗ | InternalEnvironment *envi = static_cast<InternalEnvironment*>(env); | |
| 368 | ✗ | PClip child = args[0].AsClip(); | |
| 369 | |||
| 370 | ✗ | int PrefetchThreads = args[1].AsInt((int)envi->GetEnvProperty(AEP_PHYSICAL_CPUS)+1); | |
| 371 | ✗ | int PrefetchFrames = args[2].AsInt(PrefetchThreads * 2); | |
| 372 | |||
| 373 | ✗ | if (PrefetchThreads > 0 && PrefetchFrames > 0) | |
| 374 | { | ||
| 375 | ✗ | return new Prefetcher(child, PrefetchThreads, PrefetchFrames, env); | |
| 376 | } | ||
| 377 | else | ||
| 378 | ✗ | return child; | |
| 379 | ✗ | } | |
| 380 |