Skip to content

Commit 45edaf7

Browse files
committed
some pedigree ID design cleanup
1 parent c4c1d0c commit 45edaf7

6 files changed

Lines changed: 286 additions & 241 deletions

File tree

VERSIONS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ multitrait branch:
203203
fix #651, font size in Help window doesn't scale with the user preference
204204
add a built-in "Multispecies Multitrait Phenotype ~ Time" plot for phenotype ~ time, with one line per trait across all species
205205
add a built-in "Phenotype ~ Time" plot for phenotype ~ time, focusing on a single user-selected trait across subpopulations
206+
clean up the way pedigree IDs get recorded, for better efficiency and parallelization
206207

207208

208209
version 5.2 (Eidos version 4.2):

core/individual.h

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,40 @@ extern Individual_Class *gSLiM_Individual_Class;
4747

4848

4949
// A global counter used to assign all Individual objects a unique ID. Note this is shared by all species.
50-
extern slim_pedigreeid_t gSLiM_next_pedigree_id; // use SLiM_GetNextPedigreeID() instead, for THREAD_SAFETY_IN_ACTIVE_PARALLEL()
50+
extern slim_pedigreeid_t gSLiM_next_pedigree_id; // use SLiM_GetNextPedigreeID() instead
5151

5252
inline slim_pedigreeid_t SLiM_GetNextPedigreeID(void)
5353
{
54-
THREAD_SAFETY_IN_ACTIVE_PARALLEL("SLiM_GetNextPedigreeID(): gSLiM_next_pedigree_id change");
55-
return gSLiM_next_pedigree_id++;
54+
// BCH 8/1/2026: the use of omp atomic should make locking unnecessary; each thread should get a unique value.
55+
56+
slim_pedigreeid_t ret;
57+
58+
#pragma omp atomic capture
59+
ret = gSLiM_next_pedigree_id++;
60+
61+
return ret;
5662
}
5763

58-
inline slim_pedigreeid_t SLiM_GetNextPedigreeID_Block(int p_block_size)
64+
inline void SLiM_ResetPedigreeIDCounter(void)
5965
{
60-
THREAD_SAFETY_IN_ACTIVE_PARALLEL("SLiM_GetNextPedigreeID_Block(): gSLiM_next_pedigree_id change");
61-
slim_pedigreeid_t block_base = gSLiM_next_pedigree_id;
62-
63-
gSLiM_next_pedigree_id += p_block_size;
66+
// BCH 8/4/2026: this resets the pedigree ID counter to 0. It is called when we load from a file, which
67+
// throws out all the old population data and reads new data in (perhaps with explicit pedigree IDs).
68+
#pragma omp atomic write
69+
gSLiM_next_pedigree_id = 0;
70+
}
71+
72+
inline void SLiM_UsedPedigreeID(slim_pedigreeid_t p_used_id)
73+
{
74+
// BCH 8/4/2026: this updates gSLiM_next_pedigree_id to account for the fact that a given pedigree ID has
75+
// been used; the next pedigree ID generated must be at least one greater than the used ID. This requires
76+
// a critical section, since the read-max-write operation cannot be done atomically, unfortunately; but
77+
// happily this only occurs in a couple of places in the code. NOTE: right now this is only called when
78+
// single-threaded, so this is kind of moot, but it expresses the correct atomic semantics.
6479

65-
return block_base;
80+
#pragma omp critical (UsedPedigreeID)
81+
{
82+
gSLiM_next_pedigree_id = std::max(gSLiM_next_pedigree_id, p_used_id + 1);
83+
}
6684
}
6785

6886
// This struct contains all information for a single trait in a single individual. In a multitrait
@@ -191,6 +209,11 @@ class Individual : public EidosDictionaryUnretained
191209
#endif
192210
}
193211

212+
// PEDIGREE TRACKING
213+
#pragma mark -
214+
#pragma mark pedigree tracking
215+
#pragma mark -
216+
194217
// This sets the receiver up as a new individual, with a newly assigned pedigree id, and gets
195218
// parental and grandparental information from the supplied parents.
196219
inline __attribute__((always_inline)) void TrackParentage_Biparental(slim_pedigreeid_t p_pedigree_id, Individual &p_parent1, Individual &p_parent2)
@@ -208,17 +231,18 @@ class Individual : public EidosDictionaryUnretained
208231
pedigree_g3_ = p_parent2.pedigree_p1_;
209232
pedigree_g4_ = p_parent2.pedigree_p2_;
210233

211-
#pragma omp critical (ReproductiveOutput)
212-
{
213-
p_parent1.reproductive_output_++;
214-
p_parent2.reproductive_output_++;
215-
}
234+
// Multiple threads might be incrementing these values in parallel, so we need atomicity
235+
#pragma omp atomic update
236+
p_parent1.reproductive_output_++;
237+
238+
#pragma omp atomic update
239+
p_parent2.reproductive_output_++;
216240
}
217241

218242
inline __attribute__((always_inline)) void RevokeParentage_Biparental(Individual &p_parent1, Individual &p_parent2)
219243
{
220-
// note this does not need to be in #pragma omp critical (ReproductiveOutput) because it never gets hit when parallel
221-
// that is because it only happens when modifyChild() rejects a child, and that does not happen when parallel
244+
// BCH 8/4/2026: Note that this will only be hit single-threaded, so atomicity and memory ordering
245+
// is not important; it happens when modifyChild() rejects a child, which doesn't happen in parallel
222246
p_parent1.reproductive_output_--;
223247
p_parent2.reproductive_output_--;
224248
}
@@ -238,16 +262,15 @@ class Individual : public EidosDictionaryUnretained
238262
pedigree_g3_ = p_parent.pedigree_p1_;
239263
pedigree_g4_ = p_parent.pedigree_p2_;
240264

241-
#pragma omp critical (ReproductiveOutput)
242-
{
243-
p_parent.reproductive_output_ += 2;
244-
}
265+
// Multiple threads might be incrementing these values in parallel, so we need atomicity
266+
#pragma omp atomic update
267+
p_parent.reproductive_output_ += 2;
245268
}
246269

247270
inline __attribute__((always_inline)) void RevokeParentage_Uniparental(Individual &p_parent)
248271
{
249-
// note this does not need to be in #pragma omp critical (ReproductiveOutput) because it never gets hit when parallel
250-
// that is because it only happens when modifyChild() rejects a child, and that does not happen when parallel
272+
// BCH 8/4/2026: Note that this will only be hit single-threaded, so atomicity and memory ordering
273+
// is not important; it happens when modifyChild() rejects a child, which doesn't happen in parallel
251274
p_parent.reproductive_output_ -= 2;
252275
}
253276

core/population.cpp

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,9 +1325,9 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
13251325
bool do_mutrun_exp_timing_per_individual = local_doing_mutrun_experiments && (species_.Chromosomes().size() > 1);
13261326
bool do_mutrun_exp_timing_once = local_doing_mutrun_experiments && (species_.Chromosomes().size() == 1);
13271327

1328-
bool (Subpopulation::*MungeIndividualCrossed_TEMPLATED)(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex);
1329-
bool (Subpopulation::*MungeIndividualSelfed_TEMPLATED)(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent);
1330-
bool (Subpopulation::*MungeIndividualCloned_TEMPLATED)(Individual *individual, slim_pedigreeid_t p_pedigree_id, Individual *p_parent);
1328+
bool (Subpopulation::*MungeIndividualCrossed_TEMPLATED)(Individual *individual, Individual *p_parent1, Individual *p_parent2, IndividualSex p_child_sex);
1329+
bool (Subpopulation::*MungeIndividualSelfed_TEMPLATED)(Individual *individual, Individual *p_parent);
1330+
bool (Subpopulation::*MungeIndividualCloned_TEMPLATED)(Individual *individual, Individual *p_parent);
13311331

13321332
if (do_mutrun_exp_timing_per_individual)
13331333
{
@@ -2049,11 +2049,10 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
20492049
else
20502050
parent1 = source_subpop.DrawParentUsingFitness(rng_state);
20512051

2052-
slim_pedigreeid_t individual_pid = pedigrees_enabled ? SLiM_GetNextPedigreeID() : 0;
20532052
Individual *new_child = p_subpop.child_individuals_[child_index];
20542053
new_child->migrant_ = false;
20552054

2056-
child_accepted = (p_subpop.*MungeIndividualCloned_TEMPLATED)(new_child, individual_pid, source_subpop.parent_individuals_[parent1]);
2055+
child_accepted = (p_subpop.*MungeIndividualCloned_TEMPLATED)(new_child, source_subpop.parent_individuals_[parent1]);
20572056
}
20582057
else
20592058
{
@@ -2066,12 +2065,10 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
20662065

20672066
if (selfed)
20682067
{
2069-
slim_pedigreeid_t individual_pid = pedigrees_enabled ? SLiM_GetNextPedigreeID() : 0;
2070-
20712068
Individual *new_child = p_subpop.child_individuals_[child_index];
20722069
new_child->migrant_ = false;
20732070

2074-
child_accepted = (p_subpop.*MungeIndividualSelfed_TEMPLATED)(new_child, individual_pid, source_subpop.parent_individuals_[parent1]);
2071+
child_accepted = (p_subpop.*MungeIndividualSelfed_TEMPLATED)(new_child, source_subpop.parent_individuals_[parent1]);
20752072
}
20762073
else
20772074
{
@@ -2104,12 +2101,10 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
21042101
}
21052102
}
21062103

2107-
slim_pedigreeid_t individual_pid = pedigrees_enabled ? SLiM_GetNextPedigreeID() : 0;
2108-
21092104
Individual *new_child = p_subpop.child_individuals_[child_index];
21102105
new_child->migrant_ = false;
21112106

2112-
child_accepted = (p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, individual_pid, source_subpop.parent_individuals_[parent1], source_subpop.parent_individuals_[parent2], child_sex);
2107+
child_accepted = (p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, source_subpop.parent_individuals_[parent1], source_subpop.parent_individuals_[parent2], child_sex);
21132108
}
21142109
}
21152110

@@ -2159,12 +2154,10 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
21592154
}
21602155
}
21612156

2162-
slim_pedigreeid_t individual_pid = pedigrees_enabled ? SLiM_GetNextPedigreeID() : 0;
2163-
21642157
Individual *new_child = p_subpop.child_individuals_[child_count];
21652158
new_child->migrant_ = false;
21662159

2167-
bool child_accepted = (p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, individual_pid, source_subpop.parent_individuals_[parent1], source_subpop.parent_individuals_[parent2], IndividualSex::kHermaphrodite);
2160+
bool child_accepted = (p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, source_subpop.parent_individuals_[parent1], source_subpop.parent_individuals_[parent2], IndividualSex::kHermaphrodite);
21682161

21692162
if (!child_accepted)
21702163
{
@@ -2410,12 +2403,10 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
24102403
else
24112404
parent1 = source_subpop->DrawParentUsingFitness(rng_state);
24122405

2413-
slim_pedigreeid_t individual_pid = pedigrees_enabled ? SLiM_GetNextPedigreeID() : 0;
2414-
24152406
Individual *new_child = p_subpop.child_individuals_[child_index];
24162407
new_child->migrant_ = (source_subpop != &p_subpop);
24172408

2418-
child_accepted = (p_subpop.*MungeIndividualCloned_TEMPLATED)(new_child, individual_pid, source_subpop->parent_individuals_[parent1]);
2409+
child_accepted = (p_subpop.*MungeIndividualCloned_TEMPLATED)(new_child, source_subpop->parent_individuals_[parent1]);
24192410
}
24202411
else
24212412
{
@@ -2428,12 +2419,10 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
24282419

24292420
if (selfed)
24302421
{
2431-
slim_pedigreeid_t individual_pid = pedigrees_enabled ? SLiM_GetNextPedigreeID() : 0;
2432-
24332422
Individual *new_child = p_subpop.child_individuals_[child_index];
24342423
new_child->migrant_ = (source_subpop != &p_subpop);
24352424

2436-
child_accepted = (p_subpop.*MungeIndividualSelfed_TEMPLATED)(new_child, individual_pid, source_subpop->parent_individuals_[parent1]);
2425+
child_accepted = (p_subpop.*MungeIndividualSelfed_TEMPLATED)(new_child, source_subpop->parent_individuals_[parent1]);
24372426
}
24382427
else
24392428
{
@@ -2466,12 +2455,10 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
24662455
}
24672456
}
24682457

2469-
slim_pedigreeid_t individual_pid = pedigrees_enabled ? SLiM_GetNextPedigreeID() : 0;
2470-
24712458
Individual *new_child = p_subpop.child_individuals_[child_index];
24722459
new_child->migrant_ = (source_subpop != &p_subpop);
24732460

2474-
child_accepted = (p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, individual_pid, source_subpop->parent_individuals_[parent1], source_subpop->parent_individuals_[parent2], child_sex);
2461+
child_accepted = (p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, source_subpop->parent_individuals_[parent1], source_subpop->parent_individuals_[parent2], child_sex);
24752462
}
24762463
}
24772464

@@ -2577,9 +2564,13 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
25772564
else if (cloning_fraction > 0)
25782565
number_to_clone = static_cast<slim_popsize_t>(gsl_ran_binomial(rng_gsl, cloning_fraction, (unsigned int)migrants_to_generate));
25792566

2580-
// We get a whole block of pedigree IDs to use in the loop below, avoiding race conditions / locking
2581-
// We are also going to use Individual objects from a block starting at base_child_count
2582-
slim_pedigreeid_t base_pedigree_id = SLiM_GetNextPedigreeID_Block(migrants_to_generate);
2567+
// We are going to use Individual objects from a block starting at base_child_count.
2568+
// NOTE: We no longer reserve a block of pedigree IDs with SLiM_GetNextPedigreeID_Block();
2569+
// the new atomic design for SLiM_GetNextPedigreeID() avoids locking and race conditions.
2570+
// Getting each pedigree ID separately is probably slower than getting a block, especially
2571+
// on ARM, but only in the parallel case; in the single-threaded case the new design should
2572+
// be faster since the MungeIndividualXXXXX() methods can fetch the pedigree ID themselves
2573+
// rather than receiving it as a parameter. And the design is much simpler this way.
25832574
slim_popsize_t base_child_count = child_count;
25842575

25852576
// We need to make sure we have adequate capacity in the global mutation block for new mutations before we go parallel;
@@ -2628,7 +2619,7 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
26282619
{
26292620
EIDOS_BENCHMARK_START(EidosBenchmarkType::k_WF_REPRO);
26302621
EIDOS_THREAD_COUNT(gEidos_OMP_threads_WF_REPRO);
2631-
#pragma omp parallel default(none) shared(gEidos_RNG_PERTHREAD, migrants_to_generate, base_child_count, base_pedigree_id, pedigrees_enabled, p_subpop, source_subpop, child_sex, prevent_incidental_selfing) if(will_parallelize) num_threads(thread_count)
2622+
#pragma omp parallel default(none) shared(gEidos_RNG_PERTHREAD, migrants_to_generate, base_child_count, base_pedigree_id, p_subpop, source_subpop, child_sex, prevent_incidental_selfing) if(will_parallelize) num_threads(thread_count)
26322623
{
26332624
Eidos_RNG_State *parallel_rng_state = EIDOS_STATE_RNG(omp_get_thread_num());
26342625

@@ -2642,7 +2633,7 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
26422633
Individual *new_child = p_subpop.child_individuals_[this_child_index];
26432634
new_child->migrant_ = (&source_subpop != &p_subpop);
26442635

2645-
(p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, base_pedigree_id + migrant_count, source_subpop.parent_individuals_[parent1], source_subpop.parent_individuals_[parent2], child_sex);
2636+
(p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, source_subpop.parent_individuals_[parent1], source_subpop.parent_individuals_[parent2], child_sex);
26462637
new_child->migrant_ = (&source_subpop != &p_subpop);
26472638
}
26482639
}
@@ -2654,7 +2645,7 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
26542645
{
26552646
EIDOS_BENCHMARK_START(EidosBenchmarkType::k_WF_REPRO);
26562647
EIDOS_THREAD_COUNT(gEidos_OMP_threads_WF_REPRO);
2657-
#pragma omp parallel default(none) shared(gEidos_RNG_PERTHREAD, migrants_to_generate, base_child_count, base_pedigree_id, pedigrees_enabled, p_subpop, source_subpop, child_sex, prevent_incidental_selfing) if(will_parallelize) num_threads(thread_count)
2648+
#pragma omp parallel default(none) shared(gEidos_RNG_PERTHREAD, migrants_to_generate, base_child_count, base_pedigree_id, p_subpop, source_subpop, child_sex, prevent_incidental_selfing) if(will_parallelize) num_threads(thread_count)
26582649
{
26592650
Eidos_RNG_State *parallel_rng_state = EIDOS_STATE_RNG(omp_get_thread_num());
26602651

@@ -2672,7 +2663,7 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
26722663
Individual *new_child = p_subpop.child_individuals_[this_child_index];
26732664
new_child->migrant_ = (&source_subpop != &p_subpop);
26742665

2675-
(p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, base_pedigree_id + migrant_count, source_subpop.parent_individuals_[parent1], source_subpop.parent_individuals_[parent2], child_sex);
2666+
(p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, source_subpop.parent_individuals_[parent1], source_subpop.parent_individuals_[parent2], child_sex);
26762667
}
26772668
}
26782669
EIDOS_BENCHMARK_END(EidosBenchmarkType::k_WF_REPRO);
@@ -2685,7 +2676,7 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
26852676
// the full loop with support for selfing/cloning (but no callbacks, since we're in that overall branch)
26862677
EIDOS_BENCHMARK_START(EidosBenchmarkType::k_WF_REPRO);
26872678
EIDOS_THREAD_COUNT(gEidos_OMP_threads_WF_REPRO);
2688-
#pragma omp parallel default(none) shared(gEidos_RNG_PERTHREAD, migrants_to_generate, number_to_clone, number_to_self, base_child_count, base_pedigree_id, pedigrees_enabled, p_subpop, source_subpop, sex_enabled, child_sex, recording_tree_sequence, prevent_incidental_selfing) if(will_parallelize) num_threads(thread_count)
2679+
#pragma omp parallel default(none) shared(gEidos_RNG_PERTHREAD, migrants_to_generate, number_to_clone, number_to_self, base_child_count, base_pedigree_id, p_subpop, source_subpop, sex_enabled, child_sex, recording_tree_sequence, prevent_incidental_selfing) if(will_parallelize) num_threads(thread_count)
26892680
{
26902681
Eidos_RNG_State *parallel_rng_state = EIDOS_STATE_RNG(omp_get_thread_num());
26912682

@@ -2705,7 +2696,7 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
27052696
Individual *new_child = p_subpop.child_individuals_[this_child_index];
27062697
new_child->migrant_ = (&source_subpop != &p_subpop);
27072698

2708-
(p_subpop.*MungeIndividualCloned_TEMPLATED)(new_child, base_pedigree_id + migrant_count, source_subpop.parent_individuals_[parent1]);
2699+
(p_subpop.*MungeIndividualCloned_TEMPLATED)(new_child, source_subpop.parent_individuals_[parent1]);
27092700
}
27102701
else
27112702
{
@@ -2722,7 +2713,7 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
27222713

27232714
if (migrant_count < number_to_clone + number_to_self)
27242715
{
2725-
(p_subpop.*MungeIndividualSelfed_TEMPLATED)(new_child, base_pedigree_id + migrant_count, source_subpop.parent_individuals_[parent1]);
2716+
(p_subpop.*MungeIndividualSelfed_TEMPLATED)(new_child, source_subpop.parent_individuals_[parent1]);
27262717
}
27272718
else
27282719
{
@@ -2739,7 +2730,7 @@ void Population::EvolveSubpopulation(Subpopulation &p_subpop, bool p_mate_choice
27392730
while (prevent_incidental_selfing && (parent2 == parent1));
27402731
}
27412732

2742-
(p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, base_pedigree_id + migrant_count, source_subpop.parent_individuals_[parent1], source_subpop.parent_individuals_[parent2], child_sex);
2733+
(p_subpop.*MungeIndividualCrossed_TEMPLATED)(new_child, source_subpop.parent_individuals_[parent1], source_subpop.parent_individuals_[parent2], child_sex);
27432734
}
27442735
}
27452736
}

0 commit comments

Comments
 (0)