Version

menu_open
Wwise SDK 2024.1.1
AkAtomicSpinFuncs.h
Go to the documentation of this file.
1 /*******************************************************************************
2 The content of this file includes portions of the AUDIOKINETIC Wwise Technology
3 released in source code form as part of the SDK installer package.
4 
5 Commercial License Usage
6 
7 Licensees holding valid commercial licenses to the AUDIOKINETIC Wwise Technology
8 may use this file in accordance with the end user license agreement provided
9 with the software or, alternatively, in accordance with the terms contained in a
10 written agreement between you and Audiokinetic Inc.
11 
12 Apache License Usage
13 
14 Alternatively, this file may be used under the Apache License, Version 2.0 (the
15 "Apache License"); you may not use this file except in compliance with the
16 Apache License. You may obtain a copy of the Apache License at
17 http://www.apache.org/licenses/LICENSE-2.0.
18 
19 Unless required by applicable law or agreed to in writing, software distributed
20 under the Apache License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
21 OR CONDITIONS OF ANY KIND, either express or implied. See the Apache License for
22 the specific language governing permissions and limitations under the License.
23 
24  Copyright (c) 2024 Audiokinetic Inc.
25 *******************************************************************************/
26 
27 // AkAtomicSpinFuncs.h
28 
29 // Helper functions for some simple spin-locks using atomics.
30 
31 #pragma once
32 
35 
36 #if defined(AK_MONITORX_SUPPORTED)
37 
38 #if (defined __clang__ || defined __GNUC__)
39 #include <x86intrin.h>
40 #elif defined(_MSC_VER)
41 #include <intrin.h>
42 #endif
43 
44 #endif // defined(AK_MONITORX_SUPPORTED)
45 
46 #if !defined(AK_NULL_PLATFORM)
47 /// Platform-dependent helpers
48 namespace AKPLATFORM
49 {
50 #if defined(AK_MONITORX_SUPPORTED)
51  // monitorx and waitx are available on certain AMD CPUs,
52  // so we can have a custom impl of AkLimitedSpinForZero, and AkLimitedSpinToAcquire,
53  // which can slightly improve performance when spinlock is tripped
54 
55  // Waits for a limited amount of time for in_pVal to hit zero (without yielding the thread)
56  inline void AkLimitedSpinForZero(AkAtomic32* in_pVal)
57  {
58  AkInt64 endSpinTime = 0;
59  AkInt64 currentTime = 0;
60  PerformanceCounter(&endSpinTime);
61  endSpinTime += AkInt64(AK::g_fFreqRatio * 0.01); // only spin for about 10us
62  while (true)
63  {
64  // set up monitorx on pVal
65  _mm_monitorx((void*)in_pVal, 0U, 0U);
66  // if pval is zero, skip out
67  if (AkAtomicLoad32(in_pVal) == 0)
68  {
69  break;
70  }
71  // wait until a store to pVal occurs (or ~1us passes)
72  _mm_mwaitx(2U, 0U, 1000U);
73 
74  // Check if we've hit the deadline for the timeout
75  PerformanceCounter(&currentTime);
76  if (currentTime > endSpinTime)
77  {
78  break;
79  }
80  }
81  }
82 
83  // Waits for a limited amount of time for in_pVal to get atomically shift from the expected value to the proposed one
84  // returns true if acquisition succeeded
85  inline bool AkLimitedSpinToAcquire(AkAtomic32* in_pVal, AkInt32 in_proposed, AkInt32 in_expected)
86  {
87  if (AkAtomicCas32(in_pVal, in_proposed, in_expected))
88  {
89  return true;
90  }
91 
92  // Cas failed, start the slower evaluation
93 
94  AkInt64 endSpinTime = 0;
95  AkInt64 currentTime = 0;
96  PerformanceCounter(&endSpinTime);
97  endSpinTime += AkInt64(AK::g_fFreqRatio * 0.01); // only spin for about 10us
98  while (true)
99  {
100  // set up monitorx on pVal
101  _mm_monitorx((void*)in_pVal, 0U, 0U);
102  // attempt cas to acquire and if successful, skip out
103  if (AkAtomicCas32(in_pVal, in_proposed, in_expected))
104  {
105  return true;
106  }
107  // wait until a store to pVal occurs (or ~1us passes)
108  _mm_mwaitx(2U, 0U, 1000U);
109 
110  // Check if we've hit the deadline for the timeout
111  PerformanceCounter(&currentTime);
112  if (currentTime > endSpinTime)
113  {
114  return false;
115  }
116  }
117  }
118 #else
119  // Waits for a limited amount of time for in_pVal to hit zero (without yielding the thread)
120  inline void AkLimitedSpinForZero(AkAtomic32* in_pVal)
121  {
122  AkInt64 endSpinTime = 0;
123  AkInt64 currentTime = 0;
124  PerformanceCounter(&endSpinTime);
125  endSpinTime += AkInt64(AK::g_fFreqRatio * 0.01); // only spin for about 10us
126  while (true)
127  {
128  // if pval is zero, skip out
129  if (AkAtomicLoad32(in_pVal) == 0)
130  {
131  break;
132  }
133  AkSpinHint();
134 
135  // Check if we've hit the deadline for the timeout
136  PerformanceCounter(&currentTime);
137  if (currentTime > endSpinTime)
138  {
139  break;
140  }
141  }
142  }
143 
144  // Waits for a limited amount of time for in_pVal to get atomically shift from the expected value to the proposed one
145  // returns true if acquisition succeeded
146  inline bool AkLimitedSpinToAcquire(AkAtomic32* in_pVal, AkInt32 in_proposed, AkInt32 in_expected)
147  {
148  if (AkAtomicCas32(in_pVal, in_proposed, in_expected))
149  {
150  return true;
151  }
152 
153  // Cas failed, start the slower evaluation
154  AkInt64 endSpinTime = 0;
155  AkInt64 currentTime = 0;
156  PerformanceCounter(&endSpinTime);
157  endSpinTime += AkInt64(AK::g_fFreqRatio * 0.01); // only spin for about 10us
158  while (true)
159  {
160  // attempt cas to acquire and if successful, skip out
161  if (AkAtomicCas32(in_pVal, in_proposed, in_expected))
162  {
163  return true;
164  }
165  AkSpinHint();
166 
167  // Check if we've hit the deadline for the timeout
168  PerformanceCounter(&currentTime);
169  if (currentTime > endSpinTime)
170  {
171  return false;
172  }
173  }
174  }
175 #endif // !defined(AK_MONITORX_SUPPORTED)
176 
177  inline void AkSpinWaitForZero(AkAtomic32* in_pVal)
178  {
179  if (AkAtomicLoad32(in_pVal) == 0)
180  {
181  return;
182  }
183 
184  // do a limited spin on-the-spot until in_pVal hits zero
185  AkLimitedSpinForZero(in_pVal);
186 
187  // if in_pVal is still non-zero, then the other thread is either blocked or waiting for us. Yield for real.
188  while (AkAtomicLoad32(in_pVal))
189  AkThreadYield();
190  }
191 
192  // Waits for a limited amount of time for in_pVal to get atomically shift from 0 to 1
193  inline void AkSpinToAcquire(AkAtomic32* in_pVal, AkInt32 in_proposed, AkInt32 in_expected)
194  {
195  // do a limited spin on-the-spot until in_pVal can successfully hit 1
196  // or if it fails, then the other thread is either blocked or waiting for us. Yield for real.
197  while (!AkLimitedSpinToAcquire(in_pVal, in_proposed, in_expected))
198  {
199  AkThreadYield();
200  }
201  }
202 }
203 #endif
void AkSpinHint()
Definition: AkAtomic.h:37
Platform-dependent helpers.
__forceinline int AkAtomicCas32(AkAtomic32 *pDest, long proposed, long expected)
Definition: AkAtomic.h:65
volatile int32_t AkAtomic32
Definition: AkAtomicTypes.h:33
int32_t AkInt32
Signed 32-bit integer.
void PerformanceCounter(AkInt64 *out_piLastTime)
Platform Independent Helper.
void AkLimitedSpinForZero(AkAtomic32 *in_pVal)
bool AkLimitedSpinToAcquire(AkAtomic32 *in_pVal, AkInt32 in_proposed, AkInt32 in_expected)
AkReal32 g_fFreqRatio
int64_t AkInt64
Signed 64-bit integer.
void AkSpinToAcquire(AkAtomic32 *in_pVal, AkInt32 in_proposed, AkInt32 in_expected)
#define AkThreadYield()
Definition: AkAtomic.h:35
void AkSpinWaitForZero(AkAtomic32 *in_pVal)
__forceinline long AkAtomicLoad32(AkAtomic32 *pSrc)
Definition: AkAtomic.h:55

Was this page helpful?

Need Support?

Questions? Problems? Need more info? Contact us, and we can help!

Visit our Support page

Tell us about your project. We're here to help.

Register your project and we'll help you get started with no strings attached!

Get started with Wwise