DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
MPI.h
1// Copyright (C) 2007-2023 Magnus Vikstrøm, Garth N. Wells and Paul T. Kühner
2//
3// This file is part of DOLFINx (https://www.fenicsproject.org)
4//
5// SPDX-License-Identifier: LGPL-3.0-or-later
6
7#pragma once
8
9#include "Timer.h"
10#include "local_range.h"
11#include "log.h"
12#include "types.h"
13#include <algorithm>
14#include <array>
15#include <cassert>
16#include <complex>
17#include <cstdint>
18#include <numeric>
19#include <set>
20#include <span>
21#include <tuple>
22#include <type_traits>
23#include <utility>
24#include <vector>
25
26#define MPICH_IGNORE_CXX_SEEK 1
27#include <mpi.h>
28
30namespace dolfinx::MPI
31{
33enum class tag : std::uint16_t
34{
35 consensus_pcx = 1200,
36 consensus_pex = 1201,
37 consensus_nbx = 1202,
38};
39
42class Comm
43{
44public:
46 explicit Comm(MPI_Comm comm, bool duplicate = true);
47
49 Comm(const Comm& comm) noexcept;
50
52 Comm(Comm&& comm) noexcept;
53
54 // Disable copy assignment operator
55 Comm& operator=(const Comm& comm) = delete;
56
58 Comm& operator=(Comm&& comm) noexcept;
59
61 ~Comm();
62
64 MPI_Comm comm() const noexcept;
65
66private:
67 // MPI communicator
68 MPI_Comm _comm;
69};
70
72int rank(MPI_Comm comm);
73
76int size(MPI_Comm comm);
77
82void check_error(MPI_Comm comm, int code);
83
90constexpr int index_owner(int size, std::size_t index, std::size_t N)
91{
92 assert(index < N);
93
94 // Compute number of items per rank and remainder
95 const std::size_t n = N / size;
96 const std::size_t r = N % size;
97
98 if (index < r * (n + 1))
99 {
100 // First r ranks own n + 1 indices
101 return index / (n + 1);
102 }
103 else
104 {
105 // Remaining ranks own n indices
106 return r + (index - r * (n + 1)) / n;
107 }
108}
109
133std::vector<int> compute_graph_edges_pcx(MPI_Comm comm,
134 std::span<const int> edges);
135
161std::vector<int>
162compute_graph_edges_nbx(MPI_Comm comm, std::span<const int> edges,
163 int tag = static_cast<int>(tag::consensus_nbx));
164
184template <typename U>
185std::pair<std::vector<std::int32_t>,
186 std::vector<typename std::remove_reference_t<typename U::value_type>>>
187distribute_to_postoffice(MPI_Comm comm, const U& x,
188 std::array<std::int64_t, 2> shape,
189 std::int64_t rank_offset);
190
212template <typename U>
213std::vector<typename std::remove_reference_t<typename U::value_type>>
214distribute_from_postoffice(MPI_Comm comm, std::span<const std::int64_t> indices,
215 const U& x, std::array<std::int64_t, 2> shape,
216 std::int64_t rank_offset);
217
238template <typename U>
239std::vector<typename std::remove_reference_t<typename U::value_type>>
240distribute_data(MPI_Comm comm0, std::span<const std::int64_t> indices,
241 MPI_Comm comm1, const U& x, int shape1);
242
243template <typename T>
244struct dependent_false : std::false_type
245{
246};
247
249
251template <typename T>
253
256template <typename T>
258
261#define MAP_TO_MPI_TYPE(cpp_t, mpi_t) \
262 template <> \
263 struct mpi_type_mapping<cpp_t> \
264 { \
265 static inline MPI_Datatype type = mpi_t; \
266 };
267
271MAP_TO_MPI_TYPE(float, MPI_FLOAT)
272MAP_TO_MPI_TYPE(double, MPI_DOUBLE)
273MAP_TO_MPI_TYPE(std::complex<float>, MPI_C_FLOAT_COMPLEX)
274MAP_TO_MPI_TYPE(std::complex<double>, MPI_C_DOUBLE_COMPLEX)
275MAP_TO_MPI_TYPE(std::int8_t, MPI_INT8_T)
276MAP_TO_MPI_TYPE(std::int16_t, MPI_INT16_T)
277MAP_TO_MPI_TYPE(std::int32_t, MPI_INT32_T)
278MAP_TO_MPI_TYPE(std::int64_t, MPI_INT64_T)
279MAP_TO_MPI_TYPE(std::uint8_t, MPI_UINT8_T)
280MAP_TO_MPI_TYPE(std::uint16_t, MPI_UINT16_T)
281MAP_TO_MPI_TYPE(std::uint32_t, MPI_UINT32_T)
282MAP_TO_MPI_TYPE(std::uint64_t, MPI_UINT64_T)
285
286//---------------------------------------------------------------------------
287template <typename U>
288std::pair<std::vector<std::int32_t>,
289 std::vector<typename std::remove_reference_t<typename U::value_type>>>
290distribute_to_postoffice(MPI_Comm comm, const U& x,
291 std::array<std::int64_t, 2> shape,
292 std::int64_t rank_offset)
293{
294 assert(rank_offset >= 0 or x.empty());
295 using T = typename std::remove_reference_t<typename U::value_type>;
296
297 const int size = dolfinx::MPI::size(comm);
298 const int rank = dolfinx::MPI::rank(comm);
299 assert(x.size() % shape[1] == 0);
300 const std::int32_t shape0_local = x.size() / shape[1];
301
302 spdlog::debug("Sending data to post offices (distribute_to_postoffice)");
303
304 // Post office ranks will receive data from this rank
305 std::vector<int> row_to_dest(shape0_local);
306 for (std::int32_t i = 0; i < shape0_local; ++i)
307 {
308 int dest = MPI::index_owner(size, i + rank_offset, shape[0]);
309 row_to_dest[i] = dest;
310 }
311
312 // Build list of (dest, positions) for each row that doesn't belong to
313 // this rank, then sort
314 std::vector<std::array<std::int32_t, 2>> dest_to_index;
315 dest_to_index.reserve(shape0_local);
316 for (std::int32_t i = 0; i < shape0_local; ++i)
317 {
318 std::size_t idx = i + rank_offset;
319 if (int dest = MPI::index_owner(size, idx, shape[0]); dest != rank)
320 dest_to_index.push_back({dest, i});
321 }
322 std::ranges::sort(dest_to_index);
323
324 // Build list of neighbour src ranks and count number of items (rows
325 // of x) to receive from each src post office (by neighbourhood rank)
326 std::vector<int> dest;
327 std::vector<std::int32_t> num_items_per_dest,
328 pos_to_neigh_rank(shape0_local, -1);
329 {
330 auto it = dest_to_index.begin();
331 while (it != dest_to_index.end())
332 {
333 const int neigh_rank = dest.size();
334
335 // Store global rank
336 dest.push_back((*it)[0]);
337
338 // Find iterator to next global rank
339 auto it1
340 = std::find_if(it, dest_to_index.end(),
341 [r = dest.back()](auto& idx) { return idx[0] != r; });
342
343 // Store number of items for current rank
344 num_items_per_dest.push_back(std::distance(it, it1));
345
346 // Map from local x index to local destination rank
347 for (auto e = it; e != it1; ++e)
348 pos_to_neigh_rank[(*e)[1]] = neigh_rank;
349
350 // Advance iterator
351 it = it1;
352 }
353 }
354
355 // Determine source ranks
356 const std::vector<int> src = MPI::compute_graph_edges_nbx(comm, dest);
357 spdlog::info(
358 "Number of neighbourhood source ranks in distribute_to_postoffice: {}",
359 static_cast<int>(src.size()));
360
361 // Create neighbourhood communicator for sending data to post offices
362 MPI_Comm neigh_comm;
363 int err = MPI_Dist_graph_create_adjacent(
364 comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(), dest.data(),
365 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm);
366 dolfinx::MPI::check_error(comm, err);
367
368 // Compute send displacements
369 std::vector<std::int32_t> send_disp{0};
370 std::partial_sum(num_items_per_dest.begin(), num_items_per_dest.end(),
371 std::back_inserter(send_disp));
372
373 // Pack send buffers
374 std::vector<T> send_buffer_data(shape[1] * send_disp.back());
375 std::vector<std::int64_t> send_buffer_index(send_disp.back());
376 {
377 std::vector<std::int32_t> send_offsets = send_disp;
378 for (std::int32_t i = 0; i < shape0_local; ++i)
379 {
380 if (int neigh_dest = pos_to_neigh_rank[i]; neigh_dest != -1)
381 {
382 std::size_t pos = send_offsets[neigh_dest];
383 send_buffer_index[pos] = i + rank_offset;
384 std::copy_n(std::next(x.begin(), i * shape[1]), shape[1],
385 std::next(send_buffer_data.begin(), shape[1] * pos));
386 ++send_offsets[neigh_dest];
387 }
388 }
389 }
390
391 // Send number of items to post offices (destination) that I will be
392 // sending
393 std::vector<int> num_items_recv(src.size());
394 num_items_per_dest.reserve(1);
395 num_items_recv.reserve(1);
396 err = MPI_Neighbor_alltoall(num_items_per_dest.data(), 1, MPI_INT,
397 num_items_recv.data(), 1, MPI_INT, neigh_comm);
398 dolfinx::MPI::check_error(comm, err);
399
400 // Prepare receive displacement and buffers
401 std::vector<std::int32_t> recv_disp(num_items_recv.size() + 1, 0);
402 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
403 std::next(recv_disp.begin()));
404
405 // Send/receive global indices
406 std::vector<std::int64_t> recv_buffer_index(recv_disp.back());
407 err = MPI_Neighbor_alltoallv(
408 send_buffer_index.data(), num_items_per_dest.data(), send_disp.data(),
409 MPI_INT64_T, recv_buffer_index.data(), num_items_recv.data(),
410 recv_disp.data(), MPI_INT64_T, neigh_comm);
411 dolfinx::MPI::check_error(comm, err);
412
413 // Send/receive data (x)
414 MPI_Datatype compound_type;
415 MPI_Type_contiguous(shape[1], dolfinx::MPI::mpi_t<T>, &compound_type);
416 MPI_Type_commit(&compound_type);
417 std::vector<T> recv_buffer_data(shape[1] * recv_disp.back());
418 err = MPI_Neighbor_alltoallv(
419 send_buffer_data.data(), num_items_per_dest.data(), send_disp.data(),
420 compound_type, recv_buffer_data.data(), num_items_recv.data(),
421 recv_disp.data(), compound_type, neigh_comm);
422 dolfinx::MPI::check_error(comm, err);
423 err = MPI_Type_free(&compound_type);
424 dolfinx::MPI::check_error(comm, err);
425 err = MPI_Comm_free(&neigh_comm);
426 dolfinx::MPI::check_error(comm, err);
427
428 spdlog::debug("Completed send data to post offices.");
429
430 // Convert to local indices
431 const std::int64_t r0 = common::local_range(rank, shape[0], size)[0];
432 std::vector<std::int32_t> index_local(recv_buffer_index.size());
433 std::ranges::transform(recv_buffer_index, index_local.begin(),
434 [r0](auto idx) { return idx - r0; });
435
436 return {index_local, recv_buffer_data};
437}
438//---------------------------------------------------------------------------
439template <typename U>
440std::vector<typename std::remove_reference_t<typename U::value_type>>
441distribute_from_postoffice(MPI_Comm comm, std::span<const std::int64_t> indices,
442 const U& x, std::array<std::int64_t, 2> shape,
443 std::int64_t rank_offset)
444{
445 assert(rank_offset >= 0 or x.empty());
446 using T = typename std::remove_reference_t<typename U::value_type>;
447
448 common::Timer timer("Distribute row-wise data (scalable)");
449 assert(shape[1] > 0);
450
451 const int size = dolfinx::MPI::size(comm);
452 const int rank = dolfinx::MPI::rank(comm);
453 assert(x.size() % shape[1] == 0);
454 const std::int64_t shape0_local = x.size() / shape[1];
455
456 // 0. Send x data to/from post offices
457
458 // Send receive x data to post office (only for rows that need to be
459 // communicated)
460 auto [post_indices, post_x] = dolfinx::MPI::distribute_to_postoffice(
461 comm, x, {shape[0], shape[1]}, rank_offset);
462 assert(post_indices.size() == post_x.size() / shape[1]);
463
464 // 1. Send request to post office ranks for data
465
466 // Build list of (src, global index, global, index position) for each
467 // entry in 'indices' that doesn't belong to this rank, then sort
468 std::vector<std::tuple<int, std::int64_t, std::int32_t>> src_to_index;
469 for (std::size_t i = 0; i < indices.size(); ++i)
470 {
471 std::size_t idx = indices[i];
472 if (int src = dolfinx::MPI::index_owner(size, idx, shape[0]); src != rank)
473 src_to_index.push_back({src, idx, i});
474 }
475 std::ranges::sort(src_to_index);
476
477 // Build list is neighbour src ranks and count number of items (rows
478 // of x) to receive from each src post office (by neighbourhood rank)
479 std::vector<std::int32_t> num_items_per_src;
480 std::vector<int> src;
481 {
482 auto it = src_to_index.begin();
483 while (it != src_to_index.end())
484 {
485 src.push_back(std::get<0>(*it));
486 auto it1
487 = std::find_if(it, src_to_index.end(), [r = src.back()](auto& idx)
488 { return std::get<0>(idx) != r; });
489 num_items_per_src.push_back(std::distance(it, it1));
490 it = it1;
491 }
492 }
493
494 // Determine 'delivery' destination ranks (ranks that want data from
495 // me)
496 const std::vector<int> dest
498 spdlog::info(
499 "Neighbourhood destination ranks from post office in "
500 "distribute_data (rank, num dests, num dests/mpi_size): {}, {}, {}",
501 rank, static_cast<int>(dest.size()),
502 static_cast<double>(dest.size()) / size);
503
504 // Create neighbourhood communicator for sending data to post offices
505 // (src), and receiving data form my send my post office
506 MPI_Comm neigh_comm0;
507 int err = MPI_Dist_graph_create_adjacent(
508 comm, dest.size(), dest.data(), MPI_UNWEIGHTED, src.size(), src.data(),
509 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm0);
510 dolfinx::MPI::check_error(comm, err);
511
512 // Communicate number of requests to each source
513 std::vector<int> num_items_recv(dest.size());
514 num_items_per_src.reserve(1);
515 num_items_recv.reserve(1);
516 err = MPI_Neighbor_alltoall(num_items_per_src.data(), 1, MPI_INT,
517 num_items_recv.data(), 1, MPI_INT, neigh_comm0);
518 dolfinx::MPI::check_error(comm, err);
519
520 // Prepare send/receive displacements
521 std::vector<std::int32_t> send_disp{0};
522 std::partial_sum(num_items_per_src.begin(), num_items_per_src.end(),
523 std::back_inserter(send_disp));
524 std::vector<std::int32_t> recv_disp = {0};
525 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
526 std::back_inserter(recv_disp));
527
528 // Pack my requested indices (global) in send buffer ready to send to
529 // post offices
530 assert(send_disp.back() == (int)src_to_index.size());
531 std::vector<std::int64_t> send_buffer_index(src_to_index.size());
532 std::ranges::transform(src_to_index, send_buffer_index.begin(),
533 [](auto x) { return std::get<1>(x); });
534
535 // Prepare the receive buffer
536 std::vector<std::int64_t> recv_buffer_index(recv_disp.back());
537 err = MPI_Neighbor_alltoallv(
538 send_buffer_index.data(), num_items_per_src.data(), send_disp.data(),
539 MPI_INT64_T, recv_buffer_index.data(), num_items_recv.data(),
540 recv_disp.data(), MPI_INT64_T, neigh_comm0);
541 dolfinx::MPI::check_error(comm, err);
542
543 err = MPI_Comm_free(&neigh_comm0);
544 dolfinx::MPI::check_error(comm, err);
545
546 // 2. Send data (rows of x) from post office back to requesting ranks
547 // (transpose of the preceding communication pattern operation)
548
549 // Build map from local index to post_indices position. Set to -1 for
550 // data that was already on this rank and was therefore was not
551 // sent/received via a postoffice.
552 const std::array<std::int64_t, 2> postoffice_range
553 = common::local_range(rank, shape[0], size);
554 std::vector<std::int32_t> post_indices_map(
555 postoffice_range[1] - postoffice_range[0], -1);
556 for (std::size_t i = 0; i < post_indices.size(); ++i)
557 {
558 assert(post_indices[i] < (int)post_indices_map.size());
559 post_indices_map[post_indices[i]] = i;
560 }
561
562 // Build send buffer
563 std::vector<T> send_buffer_data(shape[1] * recv_disp.back());
564 for (std::size_t p = 0; p < recv_disp.size() - 1; ++p)
565 {
566 int offset = recv_disp[p];
567 for (std::int32_t i = recv_disp[p]; i < recv_disp[p + 1]; ++i)
568 {
569 std::int64_t index = recv_buffer_index[i];
570 if (index >= rank_offset and index < (rank_offset + shape0_local))
571 {
572 // I already had this index before any communication
573 std::int32_t local_index = index - rank_offset;
574 std::copy_n(std::next(x.begin(), shape[1] * local_index), shape[1],
575 std::next(send_buffer_data.begin(), shape[1] * offset));
576 }
577 else
578 {
579 // Take from my 'post bag'
580 auto local_index = index - postoffice_range[0];
581 std::int32_t pos = post_indices_map[local_index];
582 assert(pos != -1);
583 std::copy_n(std::next(post_x.begin(), shape[1] * pos), shape[1],
584 std::next(send_buffer_data.begin(), shape[1] * offset));
585 }
586
587 ++offset;
588 }
589 }
590
591 err = MPI_Dist_graph_create_adjacent(
592 comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(), dest.data(),
593 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm0);
594 dolfinx::MPI::check_error(comm, err);
595
596 MPI_Datatype compound_type0;
597 MPI_Type_contiguous(shape[1], dolfinx::MPI::mpi_t<T>, &compound_type0);
598 MPI_Type_commit(&compound_type0);
599
600 std::vector<T> recv_buffer_data(shape[1] * send_disp.back());
601 err = MPI_Neighbor_alltoallv(
602 send_buffer_data.data(), num_items_recv.data(), recv_disp.data(),
603 compound_type0, recv_buffer_data.data(), num_items_per_src.data(),
604 send_disp.data(), compound_type0, neigh_comm0);
605 dolfinx::MPI::check_error(comm, err);
606
607 err = MPI_Type_free(&compound_type0);
608 dolfinx::MPI::check_error(comm, err);
609 err = MPI_Comm_free(&neigh_comm0);
610 dolfinx::MPI::check_error(comm, err);
611
612 std::vector<std::int32_t> index_pos_to_buffer(indices.size(), -1);
613 for (std::size_t i = 0; i < src_to_index.size(); ++i)
614 index_pos_to_buffer[std::get<2>(src_to_index[i])] = i;
615
616 // Extra data to return
617 std::vector<T> x_new(shape[1] * indices.size());
618 for (std::size_t i = 0; i < indices.size(); ++i)
619 {
620 const std::int64_t index = indices[i];
621 if (index >= rank_offset and index < (rank_offset + shape0_local))
622 {
623 // Had data from the start in x
624 auto local_index = index - rank_offset;
625 std::copy_n(std::next(x.begin(), shape[1] * local_index), shape[1],
626 std::next(x_new.begin(), shape[1] * i));
627 }
628 else
629 {
630 if (int src = dolfinx::MPI::index_owner(size, index, shape[0]);
631 src == rank)
632 {
633 // In my post office bag
634 auto local_index = index - postoffice_range[0];
635 std::int32_t pos = post_indices_map[local_index];
636 assert(pos != -1);
637 std::copy_n(std::next(post_x.begin(), shape[1] * pos), shape[1],
638 std::next(x_new.begin(), shape[1] * i));
639 }
640 else
641 {
642 // In my received post
643 std::int32_t pos = index_pos_to_buffer[i];
644 assert(pos != -1);
645 std::copy_n(std::next(recv_buffer_data.begin(), shape[1] * pos),
646 shape[1], std::next(x_new.begin(), shape[1] * i));
647 }
648 }
649 }
650
651 return x_new;
652}
653//---------------------------------------------------------------------------
654template <typename U>
655std::vector<typename std::remove_reference_t<typename U::value_type>>
656distribute_data(MPI_Comm comm0, std::span<const std::int64_t> indices,
657 MPI_Comm comm1, const U& x, int shape1)
658{
659 assert(shape1 > 0);
660 assert(x.size() % shape1 == 0);
661 const std::int64_t shape0_local = x.size() / shape1;
662
663 int err;
664 std::int64_t shape0 = 0;
665 err = MPI_Allreduce(&shape0_local, &shape0, 1, MPI_INT64_T, MPI_SUM, comm0);
666 dolfinx::MPI::check_error(comm0, err);
667
668 std::int64_t rank_offset = -1;
669 if (comm1 != MPI_COMM_NULL)
670 {
671 rank_offset = 0;
672 err = MPI_Exscan(&shape0_local, &rank_offset, 1,
673 dolfinx::MPI::mpi_t<std::int64_t>, MPI_SUM, comm1);
674 dolfinx::MPI::check_error(comm1, err);
675 }
676 else
677 {
678 rank_offset = -1;
679 if (!x.empty())
680 throw std::runtime_error("Non-empty data on null MPI communicator");
681 }
682
683 return distribute_from_postoffice(comm0, indices, x, {shape0, shape1},
684 rank_offset);
685}
686//---------------------------------------------------------------------------
687
688} // namespace dolfinx::MPI
Comm(MPI_Comm comm, bool duplicate=true)
Duplicate communicator and wrap duplicate.
Definition MPI.cpp:12
~Comm()
Destructor (frees wrapped communicator).
Definition MPI.cpp:36
MPI_Comm comm() const noexcept
Return the underlying MPI_Comm object.
Definition MPI.cpp:62
Timer for measuring and logging elapsed time durations.
Definition Timer.h:40
MPI support functionality.
Definition MPI.h:31
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:257
std::vector< typename std::remove_reference_t< typename U::value_type > > distribute_data(MPI_Comm comm0, std::span< const std::int64_t > indices, MPI_Comm comm1, const U &x, int shape1)
Distribute rows of a rectangular data array to ranks where they are required (scalable version).
Definition MPI.h:656
std::pair< std::vector< std::int32_t >, std::vector< typename std::remove_reference_t< typename U::value_type > > > distribute_to_postoffice(MPI_Comm comm, const U &x, std::array< std::int64_t, 2 > shape, std::int64_t rank_offset)
Distribute row data to 'post office' ranks.
Definition MPI.h:290
std::vector< int > compute_graph_edges_nbx(MPI_Comm comm, std::span< const int > edges, int tag=static_cast< int >(tag::consensus_nbx))
Determine incoming graph edges using the NBX consensus algorithm.
Definition MPI.cpp:162
constexpr int index_owner(int size, std::size_t index, std::size_t N)
Return which rank owns index in global range [0, N - 1] (inverse of MPI::local_range).
Definition MPI.h:90
std::vector< typename std::remove_reference_t< typename U::value_type > > distribute_from_postoffice(MPI_Comm comm, std::span< const std::int64_t > indices, const U &x, std::array< std::int64_t, 2 > shape, std::int64_t rank_offset)
Distribute rows of a rectangular data array from post office ranks to ranks where they are required.
Definition MPI.h:441
tag
MPI communication tags.
Definition MPI.h:34
std::vector< int > compute_graph_edges_pcx(MPI_Comm comm, std::span< const int > edges)
Determine incoming graph edges using the PCX consensus algorithm.
Definition MPI.cpp:95
void check_error(MPI_Comm comm, int code)
Check MPI error code. If the error code is not equal to MPI_SUCCESS, then std::abort is called.
Definition MPI.cpp:80
int size(MPI_Comm comm)
Definition MPI.cpp:72
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:64
constexpr std::array< std::int64_t, 2 > local_range(int index, std::int64_t N, int size)
Partition a global range [0, N - 1] across callers into non-overlapping sub-partitions of almost equa...
Definition local_range.h:26
Definition MPI.h:245
MPI Type.
Definition MPI.h:252