blob: 00799d87688cb6584fd2940f8805f0b4aa54dd82 [file] [log] [blame]
// Copyright 2019 Google Inc. All Rights Reserved.
//
// 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
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// 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.
#ifndef NINJA_PARALLEL_MAP_H_
#define NINJA_PARALLEL_MAP_H_
#include <functional>
#include <mutex>
#include <utility>
#include <vector>
#include "thread_pool.h"
#include "util.h"
namespace detail {
template <typename T>
struct MapFnResults {
MapFnResults(size_t size) : size_(size), results_(new T[size] {}) {}
using ReturnType = std::vector<T>;
template <typename Vector, typename MapFn>
void MapItem(Vector& vec, const MapFn& map_fn, size_t idx) {
assert(idx < size_);
results_.get()[idx] = map_fn(vec[idx]);
}
ReturnType Finish() {
ReturnType result;
result.reserve(size_);
std::move(results_.get(), results_.get() + size_,
std::back_inserter(result));
return result;
}
private:
size_t size_;
/// We'd like to store the intermediate results using std::vector<T>, but
/// the std::vector<bool> specialization doesn't allow concurrent modification
/// of different elements, so use a simple array instead.
std::unique_ptr<T[]> results_;
};
template <>
struct MapFnResults<void> {
MapFnResults(size_t) {}
using ReturnType = void;
template <typename Vector, typename MapFn>
void MapItem(Vector& vec, const MapFn& map_fn, size_t idx) {
map_fn(vec[idx]);
}
ReturnType Finish() {}
};
} // namespace detail
template <typename T>
std::vector<std::pair<T, T>> SplitByThreads(T total) {
return SplitByCount(total, GetOptimalThreadPoolJobCount());
}
/// Run |map_fn| on each element of |vec| and return a vector containing the
/// result, in the same order as the input vector. Returns void instead if the
/// map function returns void.
///
/// The map function is invoked from multiple threads, so the functor is marked
/// const. (e.g. A "mutable" lambda isn't allowed.)
template <typename Vector, typename MapFn>
auto ParallelMap(ThreadPool* thread_pool, Vector&& vec, const MapFn& map_fn) ->
typename detail::MapFnResults<
typename std::remove_reference<
decltype(map_fn(vec[0]))>::type>::ReturnType {
// Identify the return type of the map function, and if it isn't void, prepare
// a vector to hold the result of mapping each item in the sequence.
using MapReturn = typename std::remove_reference<decltype(map_fn(vec[0]))>::type;
detail::MapFnResults<MapReturn> results(vec.size());
// Split the sequence up into groups.
std::vector<std::pair<size_t, size_t>> ranges = SplitByThreads(vec.size());
std::vector<std::function<void()>> tasks;
for (auto& range : ranges) {
tasks.emplace_back([&results, &vec, &map_fn, range] {
for (size_t idx = range.first; idx < range.second; ++idx) {
results.MapItem(vec, map_fn, idx);
}
});
}
thread_pool->RunTasks(std::move(tasks));
return results.Finish();
}
#endif // NINJA_PARALLEL_MAP_H_