Re: Re: Re: [PATCH v7 04/15] mm/damon: Implement region based sampling

From: SeongJae Park
Date: Fri Apr 03 2020 - 06:31:36 EST


On Thu, 2 Apr 2020 20:00:22 +0200 SeongJae Park <sj38.park@xxxxxxxxx> wrote:

> On Thu, 2 Apr 2020 18:24:01 +0100 Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx> wrote:
>
> > On Thu, 2 Apr 2020 15:59:59 +0200
> > SeongJae Park <sjpark@xxxxxxxxxx> wrote:
> >
> > > On Wed, 1 Apr 2020 10:22:22 +0200 SeongJae Park <sjpark@xxxxxxxxxx> wrote:
> > >
> > > > On Tue, 31 Mar 2020 17:02:33 +0100 Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx> wrote:
> > > >
> > > > > On Wed, 18 Mar 2020 12:27:11 +0100
> > > > > SeongJae Park <sjpark@xxxxxxxxxx> wrote:
> > > > >
> > > > > > From: SeongJae Park <sjpark@xxxxxxxxx>
[...]
> > > > > > +static void damon_pte_pmd_mkold(pte_t *pte, pmd_t *pmd)
> > > > > > +{
> > > > > > + if (pte) {
> > > > > > + if (pte_young(*pte)) {
> > > > > > + clear_page_idle(pte_page(*pte));
> > > > > > + set_page_young(pte_page(*pte));
> > > > > > + }
> > > > > > + *pte = pte_mkold(*pte);
> > > > > > + return;
> > > > > > + }
> > > > > > +#ifdef CONFIG_TRANSPARENT_HUGEPAGE
> > > > > > + if (pmd) {
> > > > > > + if (pmd_young(*pmd)) {
> > > > > > + clear_page_idle(pmd_page(*pmd));
> > > > > > + set_page_young(pmd_page(*pmd));
> > > > > > + }
> > > > > > + *pmd = pmd_mkold(*pmd);
> > > > > > + }
> > > > > > +#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
> > > > >
> > > > > No need to flush the TLBs?
> > > >
> > > > Good point!
> > > >
> > > > I have intentionally skipped TLB flushing here to minimize the performance
> > > > effect to the target workload. I also thought this might not degrade the
> > > > monitoring accuracy so much because we are targetting for the DRAM level
> > > > accesses of memory-intensive workloads, which might make TLB flood frequently.
> > > >
> > > > However, your comment makes me thinking differently now. By flushing the TLB
> > > > here, we will increase up to `number_of_regions` TLB misses for sampling
> > > > interval. This might be not a huge overhead. Also, improving the monitoring
> > > > accuracy makes no harm at all. I even didn't measured the overhead.
> > > >
> > > > I will test the overhead and if it is not significant, I will make this code to
> > > > flush TLB, in the next spin.
> > >
> > > Hmm, it seems like 'page_idle.c' is also modifying the Accessed bit but doesn't
> > > flush related TLB entries. If I'm not missing something here, I would like to
> > > leave this part as is to make the behavior consistent.
> >
> > Interesting. In that usecase, the risk is that the MMU believes
> > the page still has the accessed bit set when we have cleared it and hence
> > the accessed bit is not written out to the table in memory.
> >
> > That will give them a wrong decision so not great and would lead to them
> > thinking more pages are idle than are.
> >
> > Here we could have a particular TLB entry for a huge page in which
> > a region lies entirely. Because we don't flush the TLB each time
> > we could end with a count of 0 accesses when it should be the maximum.
> > A very frequently accessed page might well sit in the TLB for a very
> > long time (particularly if the TLB is running a clever eviction
> > strategy).
> >
> > I think we would want to be test this and see if we get that
> > pathological case sometimes. Also worth benchmarking if it actually
> > costs us very much to do the flushes.
>
> Agreed, it wouldn't be late to make a decision after measuring the real cost.
> I will share the measurement results soon. Meanwhile, it would be helpful if
> anyone could confirm whether page_idle.c is skipping TLB flushing and explain
> why such decision has made.

I just finished implementing TLB flushing in straightforward way (the diff for
this change is at the bottom of this mail) on the version I applied your
recommended changes and my one bug fix (setting 'last_accessed' false).

It shows monitoring accuracy improvement as expected, though it is not so big.
I compared the visualized access patterns of each test workload to check this.
There is no big difference, but the visualized pattern of TLB flushing version
seems a little bit more clear to my human eye.

However, the overhead is clear and significant to some workloads including
parsec3/streamcluster, splash2x/barnes, splash2x/lu_ncb and splash2x/volrend.
The CPU utilization of kdamond, the deamon monitoring the access pattern, never
exceeds 2% of single CPU time for the 4 workloads before the change is applied,
but it frequently exceeds 30% of single CPU time with the TLB flushing. The
runtimes of the monitoring target workloads also increased. In case of
parsec3/streamcluster, the TLB flushing changed its runtime from 320 seconds to
470 seconds.

So, it seems the benefit of TLB flushing is smaller than the cost in some
cases. Thus, I think enabling TLB flushes by default wouldn't be a good
decision. Rather than that, it could be better to allow users to manually do
TLB flushing for their specific workloads. This will be easily available by
using the DAMON's sampling callback functions. Also, I am planning to let
users to configure DAMON with their own access check / sampling setup functions
in future, to support not only virtual memory but also physical memory and some
other special cases.

If I'm missing something or you have other thinking, please let me know.


Thanks,
SeongJae Park

=============================== >8 ============================================

Below is the essential diff for the TLB flushing I implemented. To double
check the overhead is really from the TLB flushing, I also measured the
overhead of the additional works including the vma searching by commenting out
the 'flush_tlb_range()' call. After commenting out it, the CPU utilization of
kdamond and runtime of target workloads has restored back. So, the overhead
seems really came from the TLB flushing.

@@ -408,6 +411,9 @@ static void kdamond_prepare_sampling(struct damon_ctx *ctx,
pte_t *pte = NULL;
pmd_t *pmd = NULL;
spinlock_t *ptl;
+ struct vm_area_struct *vma;
+ unsigned long tlb_start;
+ unsigned long tlb_size = PAGE_SIZE;

r->sampling_addr = damon_rand(ctx, r->vm_start, r->vm_end);

@@ -420,18 +426,29 @@ static void kdamond_prepare_sampling(struct damon_ctx *ctx,
set_page_young(pte_page(*pte));
}
*pte = pte_mkold(*pte);
- return;
}
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
- if (pmd) {
+ else if (pmd) {
if (pmd_young(*pmd)) {
clear_page_idle(pmd_page(*pmd));
set_page_young(pmd_page(*pmd));
}
*pmd = pmd_mkold(*pmd);
+ tlb_size = ((1UL) << HPAGE_PMD_SHIFT);
}
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
spin_unlock(ptl);
+
+ tlb_start = ALIGN(r->sampling_addr, tlb_size);
+
+ down_read(&mm->mmap_sem);
+ vma = find_vma(mm, r->sampling_addr);
+ if (!vma || (r->sampling_addr < vma->vm_start))
+ goto out;
+ flush_tlb_range(vma, tlb_start, tlb_start + tlb_size);
+
+out:
+ up_read(&mm->mmap_sem);
}


Thanks,
SeongJae Park