/*========================================================================= * * Copyright NumFOCUS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ /*========================================================================= Program: Visualization Toolkit Module: TestAtomic.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or https://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "itkAtomicInt.h" #include "itkMultiThreaderBase.h" #include "itkObject.h" #include namespace { int Total = 0; itk::uint64_t Total64 = 0; itk::AtomicInt TotalAtomic(0); itk::AtomicInt TotalAtomic64(0); constexpr unsigned int Target = 1000000; itk::int32_t Values32[Target + 2]; itk::int64_t Values64[Target + 2]; itk::AtomicInt AtomicPtr(Values32); itk::AtomicInt Atomic64Ptr(Values64); int NumThreads = 5; itk::Object::Pointer AnObject; } // namespace ITK_THREAD_RETURN_FUNCTION_CALL_CONVENTION MyFunction(void *) { for (unsigned int i = 0; i < Target / NumThreads; ++i) { Total++; int idx = ++TotalAtomic; Values32[idx] = 1; Total64++; idx = ++TotalAtomic64; Values64[idx] = 1; AnObject->Modified(); } return ITK_THREAD_RETURN_DEFAULT_VALUE; } ITK_THREAD_RETURN_FUNCTION_CALL_CONVENTION MyFunction2(void *) { for (unsigned int i = 0; i < Target / NumThreads; ++i) { --TotalAtomic; --TotalAtomic64; } return ITK_THREAD_RETURN_DEFAULT_VALUE; } ITK_THREAD_RETURN_FUNCTION_CALL_CONVENTION MyFunction3(void *) { for (unsigned int i = 0; i < Target / NumThreads; ++i) { int idx = TotalAtomic += 1; Values32[idx]++; idx = TotalAtomic64 += 1; Values64[idx]++; } return ITK_THREAD_RETURN_DEFAULT_VALUE; } ITK_THREAD_RETURN_FUNCTION_CALL_CONVENTION MyFunction4(void *) { for (unsigned int i = 0; i < Target / NumThreads; ++i) { TotalAtomic++; TotalAtomic += 1; TotalAtomic--; TotalAtomic -= 1; TotalAtomic64++; TotalAtomic64 += 1; TotalAtomic64--; TotalAtomic64 -= 1; } return ITK_THREAD_RETURN_DEFAULT_VALUE; } ITK_THREAD_RETURN_FUNCTION_CALL_CONVENTION MyFunctionPtr(void *) { for (unsigned int i = 0; i < Target / NumThreads; ++i) { itk::int32_t * ptr32 = ++AtomicPtr; (*ptr32) = 1; itk::int64_t * ptr64 = ++Atomic64Ptr; (*ptr64) = 1; } return ITK_THREAD_RETURN_DEFAULT_VALUE; } template int TestAtomicOperators(std::string name) { itk::AtomicInt testAtomic(1); if (testAtomic != 1) { std::cout << "Expecting testAtomic" << name << " value to be 1. Got " << testAtomic << std::endl; return 1; } testAtomic = 2; if (testAtomic != 2) { std::cout << "Expecting testAtomic" << name << " value to be 2. Got " << testAtomic << std::endl; return 1; } itk::AtomicInt testAtomicCopy(3); testAtomic = testAtomicCopy; if (testAtomic != 3) { std::cout << "Expecting testAtomic" << name << " value to be 3. Got " << testAtomic << std::endl; return 1; } if (testAtomic.load() != 3) { std::cout << "Expecting testAtomic" << name << ".load() value to be 3. Got " << testAtomic.load() << std::endl; return 1; } testAtomic.store(0); if (testAtomic != 0) { std::cout << "Expecting testAtomic" << name << " value to be 0. Got " << testAtomic << std::endl; return 1; } T preinc_val = ++testAtomic; if (preinc_val != 1) { std::cout << "Expecting PreIncrement (" << name << ") value to be 1. Got " << preinc_val << std::endl; return 1; } T postinc_val = testAtomic++; if (postinc_val != 1) { std::cout << "Expecting PostIncrement (" << name << ") value to be 1. Got " << postinc_val << std::endl; return 1; } T predec_val = --testAtomic; if (predec_val != 1) { std::cout << "Expecting PreDecrement (" << name << ") value to be 1. Got " << predec_val << std::endl; return 1; } T postdec_val = testAtomic--; if (postdec_val != 1) { std::cout << "Expecting PostDecrement (" << name << ") value to be 1. Got " << postdec_val << std::endl; return 1; } testAtomic += 2; T addAndFetch_val = testAtomic; if (addAndFetch_val != 2) { std::cout << "Expecting AddAndFetch (" << name << ") value to be 2. Got " << addAndFetch_val << std::endl; return 1; } testAtomic -= 1; T subAndFetch_val = testAtomic; if (subAndFetch_val != 1) { std::cout << "Expecting SubAndFetch (" << name << ") value to be 1. Got " << subAndFetch_val << std::endl; return 1; } return 0; } int itkAtomicIntTest(int, char *[]) { // Verify all atomic operators if (TestAtomicOperators("32") || TestAtomicOperators("64")) { return 1; } // Verify atomic behavior Total = 0; TotalAtomic = 0; Total64 = 0; TotalAtomic64 = 0; for (unsigned int i = 0; i < Target; ++i) { Values32[i] = 0; Values64[i] = 0; } itk::MultiThreaderBase::Pointer mt = itk::MultiThreaderBase::New(); // depending on which multi-threader is instantiated by default, // there might be different number of object modification times // so the "initial" Mtime is recorder afterwards AnObject = itk::Object::New(); itk::ModifiedTimeType beforeMTime = AnObject->GetMTime(); mt->SetSingleMethod(MyFunction, nullptr); mt->SetNumberOfWorkUnits(NumThreads); mt->SingleMethodExecute(); mt->SetSingleMethod(MyFunction2, nullptr); mt->SingleMethodExecute(); mt->SetSingleMethod(MyFunction3, nullptr); mt->SingleMethodExecute(); // Making sure that atomic incr returned unique // values each time. We expect all numbers from // 1 to Target-1 to be 2. if (Values32[0] != 0) { std::cout << "Expecting Values32[0] to be 0. Got " << Values32[0] << std::endl; return 1; } if (Values64[0] != 0) { std::cout << "Expecting Values64[0] to be 0. Got " << Values64[0] << std::endl; return 1; } for (unsigned int i = 1; i < Target; ++i) { if (Values32[i] != 2) { std::cout << "Expecting Values32[" << i << "] to be 2. Got " << Values32[i] << std::endl; return 1; } if (Values64[i] != 2) { std::cout << "Expecting Values64[" << i << "] to be 2. Got " << Values64[i] << std::endl; return 1; } } mt->SetSingleMethod(MyFunction4, nullptr); mt->SingleMethodExecute(); std::cout << Total << ' ' << TotalAtomic.load() << std::endl; std::cout << Total64 << ' ' << TotalAtomic64.load() << std::endl; std::cout << "MTime: " << AnObject->GetMTime() << std::endl; mt->SetSingleMethod(MyFunctionPtr, nullptr); mt->SingleMethodExecute(); // Making sure that pointer atomic incr returned unique // values each time incremented by the size of the pointer. // We expect all numbers from 1 to Target-1 to be 1. if (Values32[0] != 0) { std::cout << "Expecting Values32[0] to be 0. Got " << Values32[0] << std::endl; return 1; } if (Values64[0] != 0) { std::cout << "Expecting Values64[0] to be 0. Got " << Values64[0] << std::endl; return 1; } for (unsigned int i = 1; i < Target; ++i) { if (Values32[i] != 1) { std::cout << "Expecting Values32[" << i << "] to be 1. Got " << Values32[i] << std::endl; return 1; } if (Values64[i] != 1) { std::cout << "Expecting Values64[" << i << "] to be 1. Got " << Values64[i] << std::endl; return 1; } } if (TotalAtomic.load() != static_cast(Target)) { return 1; } if (TotalAtomic64.load() != static_cast(Target)) { return 1; } if (AnObject->GetReferenceCount() != 1) { return 1; } if (AnObject->GetMTime() != Target + beforeMTime) { return 1; } return 0; }