Tries to pack a block, without blocking. If the block was already packed, returns true. If the block was not started packing, packs it and returns true. If the block was being packed by another thread, returns false.
| 120 | // If the block was not started packing, packs it and returns true. |
| 121 | // If the block was being packed by another thread, returns false. |
| 122 | bool TryPack(Side side, int block, int start, int end, Tuning tuning) { |
| 123 | if (params->is_prepacked[side]) { |
| 124 | return true; |
| 125 | } |
| 126 | if (!local_packed[side][block]) { |
| 127 | if (need_atomics) { |
| 128 | // Explanation of this compare_exchange_strong operation: |
| 129 | // This atomically performs all of the following: |
| 130 | // 1. Read `status` with "acquire" memory order. |
| 131 | // * That this read uses "acquire" is because both memory orders |
| 132 | // specified have "acquire" as their read-component. |
| 133 | // 2. Compare (bitwise) with `exchanged_status`. |
| 134 | // 3. If equal, stores the value kInProgress to `status` with "release" |
| 135 | // memory order, and returns true, so we take this 'if' branch. |
| 136 | // * That this store uses "release" is because of the _rel part in |
| 137 | // memory_order_acq_rel passed as the first memory order argument. |
| 138 | // 4. If not equal, stores the loaded value of `status` to |
| 139 | // `exchanged_status` with "relaxed" semantics, and returns false, |
| 140 | // so we take the 'else' branch. |
| 141 | // * That this store uses "relaxed" is because the second memory |
| 142 | // order argument, memory_order_acquire, implies no particular |
| 143 | // store semantics. "relaxed" is acceptable here because this |
| 144 | // stores to a local stack variable. |
| 145 | // |
| 146 | // Rationale for compare_exchange_strong as opposed to |
| 147 | // compare_exchange_weak: |
| 148 | // The spurious-failure case with compare_exchange_weak will actually |
| 149 | // happen a lot here, because the atomic 'status' bytes are stored |
| 150 | // contiguously in arrays and neighboring values will be accessed |
| 151 | // by multiple threads concurrently. On a typical ARM CPU, an exclusives |
| 152 | // reservation granule is 64 bytes, so a lot of false-sharing may |
| 153 | // happen. Using compare_exchange_weak would thus result in often having |
| 154 | // TryPack return 'false' when it could instead have done the packing |
| 155 | // work and returned 'true'. Heuristically, that is not a good thing. |
| 156 | // Moreover, this changes the TryPack contract, loosening it and making |
| 157 | // it harder for the caller to reason about. Finally, the overhead of |
| 158 | // atomic operations is mitigated by the enclosing check on |
| 159 | // local_packed, so maybe the overhead of compare_exchange_strong isn't |
| 160 | // such a problem. But we don't really know for sure, that would be |
| 161 | // interesting to experiment more with. |
| 162 | PackingStatus exchanged_status = PackingStatus::kNotStarted; |
| 163 | std::atomic<PackingStatus>& status = packing_status[side][block]; |
| 164 | if (status.compare_exchange_strong( |
| 165 | exchanged_status, PackingStatus::kInProgress, |
| 166 | std::memory_order_acq_rel, std::memory_order_acquire)) { |
| 167 | // In this branch, the status was kNotStarted and we just atomically |
| 168 | // changed it to kInProgress as we are about to handle the packing |
| 169 | // ourselves. |
| 170 | params->RunPack(side, tuning, start, end); |
| 171 | TraceRecordBlockPacked(thread_id, side, block, trace); |
| 172 | status.store(PackingStatus::kFinished, std::memory_order_release); |
| 173 | } else if (exchanged_status == PackingStatus::kInProgress) { |
| 174 | // Another thread is currently packing this block. |
| 175 | return false; |
| 176 | } |
| 177 | RUY_DCHECK(status.load(std::memory_order_acquire) == |
| 178 | PackingStatus::kFinished); |
| 179 | } else { |
nothing calls this directly
no test coverage detected