iommu/dma: Zero pages manually in a length of scatterlist
Subsystems (2)
PATCH 1/1 iommu/dma: Zero pages manually in a length of scatterlist
The __GFP_ZERO will be passed down to the generic page allocation routine which zeros everything page by page. This is safe to be a generic way but not efficient for iommu allocation that organizes contiguous pages using scatterlist.
So this changes drops __GFP_ZERO from the flag, and adds a manual memset after page/sg allocations, using the length of scatterlist.
My test result of a 2.5MB size allocation shows iommu_dma_alloc() takes 46% less time, reduced from averagely 925 usec to 500 usec.
--- drivers/iommu/dma-iommu.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
drivers/iommu/dma-iommu.c +18 -0
@@ -551,10 +551,13 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,struct iommu_domain *domain =iommu_get_dma_domain (dev);struct iommu_dma_cookie *cookie = domain->iova_cookie;struct iova_domain *iovad = &cookie->iovad; +struct scatterlist *s;struct page **pages;struct sg_table sgt;dma_addr_t iova;unsigned int count, min_size, alloc_sizes = domain->pgsize_bitmap; +bool gfp_zero = false; +int i; *handle =IOMMU_MAPPING_ERROR ;
@@ -568,6 +571,15 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,if (attrs &DMA_ATTR_ALLOC_SINGLE_PAGES ) alloc_sizes = min_size; +/* +* The generic zeroing in a length of one page size is slow, +* so do it manually in a length of scatterlist size instead +*/ +if (gfp & __GFP_ZERO) { + gfp &= ~__GFP_ZERO; + gfp_zero = true; + } + count =PAGE_ALIGN (size) >>PAGE_SHIFT ; pages =__iommu_dma_alloc_pages (count, alloc_sizes >>PAGE_SHIFT , gfp);if (!pages)
@@ -581,6 +593,12 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,if (sg_alloc_table_from_pages (&sgt, pages, count,0 , size,GFP_KERNEL )) goto out_free_iova; +if (gfp_zero) { +/* Now zero all the pages in the scatterlist */ +for_each_sg (sgt.sgl, s, sgt.orig_nents, i) +memset (sg_virt (s),0 , s->length); + } +if (!(prot &IOMMU_CACHE )) {struct sg_mapping_iter miter;/*
Signature · 1 line
2.17.1
Robin Murphy
maintainerQuoted 10 lines · Nicolin Chen
The
__GFP_ZEROwill be passed down to the generic page allocation routine which zeros everything page by page. This is safe to be a generic way but not efficient for iommu allocation that organizes contiguous pages using scatterlist.So this changes drops
__GFP_ZEROfrom the flag, and adds a manual memset after page/sg allocations, using the length of scatterlist.My test result of a 2.5MB size allocation shows
iommu_dma_alloc()takes 46% less time, reduced from averagely 925 usec to 500 usec.
Assuming this is for arm64, I'm somewhat surprised that memset() could be that much faster than clear_page(), since they should effectively amount to the same thing (a DC ZVA loop). What hardware is this on? Profiling to try and see exactly where the extra time goes would be interesting too.
Quoted 25 lines
Signed-off-by Nicolin Chen <[email protected]>--- drivers/iommu/dma-iommu.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)drivers/iommu/dma-iommu.c +3
-0@@ -551,10 +551,13 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,struct iommu_domain *domain =iommu_get_dma_domain (dev);struct iommu_dma_cookie *cookie = domain->iova_cookie;struct iova_domain *iovad = &cookie->iovad; +struct scatterlist *s;struct page **pages;struct sg_table sgt;dma_addr_t iova;unsigned int count, min_size, alloc_sizes = domain->pgsize_bitmap; +bool gfp_zero = false; +int i; *handle =IOMMU_MAPPING_ERROR ;@@ -568,6 +571,15 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,if (attrs &DMA_ATTR_ALLOC_SINGLE_PAGES )
drivers/iommu/dma-iommu.c struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, · line 581
Quoted 10 lines
drivers/iommu/dma-iommu.c +8
-0alloc_sizes = min_size; +/* +* The generic zeroing in a length of one page size is slow, +* so do it manually in a length of scatterlist size instead +*/ +if (gfp & __GFP_ZERO) { + gfp &= ~__GFP_ZERO; + gfp_zero = true; + }
Or just mask it out in __iommu_dma_alloc_pages()?
drivers/iommu/dma-iommu.c struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, · line 599
Quoted 12 lines
drivers/iommu/dma-iommu.c +5
-0+ count =PAGE_ALIGN (size) >>PAGE_SHIFT ; pages =__iommu_dma_alloc_pages (count, alloc_sizes >>PAGE_SHIFT , gfp);if (!pages)@@ -581,6 +593,12 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,if (sg_alloc_table_from_pages (&sgt, pages, count,0 , size,GFP_KERNEL )) goto out_free_iova; +if (gfp_zero) { +/* Now zero all the pages in the scatterlist */ +for_each_sg (sgt.sgl, s, sgt.orig_nents, i) +memset (sg_virt (s),0 , s->length);
What if the pages came from highmem? I know that doesn't happen on arm64 today, but the point of this code *is* to be generic, and other users will arrive eventually.
Robin.
Quoted 6 lines
drivers/iommu/dma-iommu.c +2
-0+ } +if (!(prot &IOMMU_CACHE )) {struct sg_mapping_iter miter;/*
Quoted 9 lines · Robin Murphy
Quoted 8 lines · Nicolin Chen
The
__GFP_ZEROwill be passed down to the generic page allocation routine which zeros everything page by page. This is safe to be a generic way but not efficient for iommu allocation that organizes contiguous pages using scatterlist.So this changes drops
__GFP_ZEROfrom the flag, and adds a manual memset after page/sg allocations, using the length of scatterlist.
Quoted 6 lines
Quoted 2 lines
My test result of a 2.5MB size allocation shows
iommu_dma_alloc()takes 46% less time, reduced from averagely 925 usec to 500 usec.Assuming this is for arm64, I'm somewhat surprised that
memset()could be that much faster thanclear_page(), since they should effectively amount to the same thing (a DC ZVA loop). What hardware is this on? Profiling to try
I am running with tegra186-p2771-0000.dtb so it's arm64 yes.
Quoted 1 line
and see exactly where the extra time goes would be interesting too.
I re-ran the test to get some accuracy within the function and got:
1) pages = __iommu_dma_alloc_pages(count, alloc_sizes >> PAGE_SHIFT, gfp); // reduced from 422 usec to 56 usec == 366 usec less
2) if (!(prot & IOMMU_CACHE)) {...} //flush routine
// reduced from 439 usec to 236 usec == 203 usec less
Note: new memset takes about 164 usec, resulting in 400 usec diff
for the entire iommu_dma_alloc() function call.
It looks like this might be more than the diff between clear_page and memset, and might be related to mapping and cache. Any idea?
Quoted 5 lines
Quoted 5 lines
drivers/iommu/dma-iommu.c +2
-0@@ -568,6 +571,15 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,if (attrs &DMA_ATTR_ALLOC_SINGLE_PAGES ) alloc_sizes = min_size; +/* +* The generic zeroing in a length of one page size is slow,
Quoted 8 lines
drivers/iommu/dma-iommu.c struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, · line 581
Quoted 6 lines
drivers/iommu/dma-iommu.c +6
-0+* so do it manually in a length of scatterlist size instead +*/ +if (gfp & __GFP_ZERO) { + gfp &= ~__GFP_ZERO; + gfp_zero = true; + }Or just mask it out in
__iommu_dma_alloc_pages()?
Yea, the change here would be neater then.
Quoted 11 lines
drivers/iommu/dma-iommu.c struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, · line 598
Quoted 7 lines
drivers/iommu/dma-iommu.c +4
-0@@ -581,6 +593,12 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,if (sg_alloc_table_from_pages (&sgt, pages, count,0 , size,GFP_KERNEL )) goto out_free_iova; +if (gfp_zero) { +/* Now zero all the pages in the scatterlist */ +for_each_sg (sgt.sgl, s, sgt.orig_nents, i) +memset (sg_virt (s),0 , s->length);What if the pages came from highmem? I know that doesn't happen on arm64 today, but the point of this code *is* to be generic, and other users will arrive eventually.
Hmm, so it probably should use sg_miter_start/stop() too? Looking at the flush routine doing in PAGE_SIZE for each iteration, would be possible to map and memset contiguous pages together? Actually the flush routine might be also optimized if we can map contiguous pages.
Thank you
Nicolin
Quoted 9 lines · Nicolin Chen
Quoted 3 lines
What if the pages came from highmem? I know that doesn't happen on arm64 today, but the point of this code *is* to be generic, and other users will arrive eventually.
Hmm, so it probably should use sg_miter_start/stop() too? Looking at the flush routine doing in
PAGE_SIZEfor each iteration, would be possible to map and memset contiguous pages together? Actually the flush routine might be also optimized if we can map contiguous pages.
FYI, I have patches I plan to submit soon that gets rid of the struct scatterlist use in this code to simplify it:
http://git.infradead.org/users/hch/misc.git/commitdiff/84e837fc3248b513f73adde49e04e7c58f605113
Robin Murphy
maintainerQuoted 5 lines · Christoph Hellwig
Quoted 4 lines · Nicolin Chen
Quoted 3 lines
What if the pages came from highmem? I know that doesn't happen on arm64 today, but the point of this code *is* to be generic, and other users will arrive eventually.
Quoted 8 lines
Quoted 5 lines
Hmm, so it probably should use sg_miter_start/stop() too? Looking at the flush routine doing in
PAGE_SIZEfor each iteration, would be possible to map and memset contiguous pages together? Actually the flush routine might be also optimized if we can map contiguous pages.FYI, I have patches I plan to submit soon that gets rid of the struct scatterlist use in this code to simplify it:
...and I have some significant objections to that simplification which I plan to respond with ;)
(namely that it defaults the whole higher-order page allocation business which will have varying degrees of performance impact on certain cases)
Robin.
Quoted 5 lines · Robin Murphy
...and I have some significant objections to that simplification which I plan to respond with ;)
(namely that it defaults the whole higher-order page allocation business which will have varying degrees of performance impact on certain cases)
Well, please place your objection there. The behavior does match what every other iommu-based dma ops implementation ouside of arm/arm64 does, so there is some precedent for it to say the least. But if the only current users objects I'll surely find a way to accomodate it, but a good rationale including numbers would be useful to document it.
Robin Murphy
maintainerQuoted 26 lines · Nicolin Chen
Quoted 9 lines · Robin Murphy
Quoted 8 lines · Nicolin Chen
The
__GFP_ZEROwill be passed down to the generic page allocation routine which zeros everything page by page. This is safe to be a generic way but not efficient for iommu allocation that organizes contiguous pages using scatterlist.So this changes drops
__GFP_ZEROfrom the flag, and adds a manual memset after page/sg allocations, using the length of scatterlist.Quoted 6 lines
Quoted 2 lines
My test result of a 2.5MB size allocation shows
iommu_dma_alloc()takes 46% less time, reduced from averagely 925 usec to 500 usec.Assuming this is for arm64, I'm somewhat surprised that
memset()could be that much faster thanclear_page(), since they should effectively amount to the same thing (a DC ZVA loop). What hardware is this on? Profiling to tryI am running with tegra186-p2771-0000.dtb so it's arm64 yes.
Quoted 1 line
and see exactly where the extra time goes would be interesting too.
I re-ran the test to get some accuracy within the function and got:
1) pages = __iommu_dma_alloc_pages(count, alloc_sizes >> PAGE_SHIFT, gfp); // reduced from 422 usec to 56 usec == 366 usec less 2) if (!(prot & IOMMU_CACHE)) {...} //flush routine // reduced from 439 usec to 236 usec == 203 usec less
Quoted 5 lines
Note: new memset takes about 164 usec, resulting in 400 usec diff
for the entireiommu_dma_alloc()function call.It looks like this might be more than the diff between
clear_pageand memset, and might be related to mapping and cache. Any idea?
Hmm, I guess it might not be so much clear_page() itself as all the gubbins involved in getting there from prep_new_page(). I could perhaps make some vague guesses about how the A57 cores might get tickled by the different code patterns, but the Denver cores are well beyond my ability to reason about. Out of even further curiosity, how does the quick hack below compare?
Quoted 28 lines
Quoted 5 lines
Quoted 5 lines
(quoted diff) +2
-0@@ -568,6 +571,15 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, if (attrs & DMA_ATTR_ALLOC_SINGLE_PAGES) alloc_sizes = min_size; + /* + * The generic zeroing in a length of one page size is slow,Quoted 8 lines
Review struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, · line 580
Quoted 6 lines
(quoted diff) +6
-0+ * so do it manually in a length of scatterlist size instead + */ + if (gfp & __GFP_ZERO) { + gfp &= ~__GFP_ZERO; + gfp_zero = true; + }Or just mask it out in
__iommu_dma_alloc_pages()?Yea, the change here would be neater then.
Quoted 11 lines
Review struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, · line 598
Quoted 7 lines
(quoted diff) +4
-0@@ -581,6 +593,12 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, if (sg_alloc_table_from_pages(&sgt, pages, count, 0, size, GFP_KERNEL)) goto out_free_iova; + if (gfp_zero) { + /* Now zero all the pages in the scatterlist */ + for_each_sg(sgt.sgl, s, sgt.orig_nents, i) + memset(sg_virt(s), 0, s->length);What if the pages came from highmem? I know that doesn't happen on arm64 today, but the point of this code *is* to be generic, and other users will arrive eventually.
Quoted 5 lines
Hmm, so it probably should use sg_miter_start/stop() too? Looking at the flush routine doing in
PAGE_SIZEfor each iteration, would be possible to map and memset contiguous pages together? Actually the flush routine might be also optimized if we can map contiguous pages.
I suppose the ideal point at which to do it would be after the remapping when we have the entire buffer contiguous in vmalloc space and can make best use of prefetchers etc. - DMA_ATTR_NO_KERNEL_MAPPING is a bit of a spanner in the works, but we could probably accommodate a special case for that. As Christoph points out, this isn't really the place to be looking for performance anyway (unless it's pathologically bad as per the DMA_ATTR_ALLOC_SINGLE_PAGES fun), but if we're looking at pulling the remapping out of the arch code, maybe we could aim to rework the zeroing completely as part of that.
Robin.
----->8-----
drivers/iommu/dma-iommu.c +6 -3
@@ -569,7 +569,7 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, alloc_sizes = min_size; count =PAGE_ALIGN (size) >>PAGE_SHIFT ; - pages =__iommu_dma_alloc_pages (count, alloc_sizes >>PAGE_SHIFT , gfp); + pages =__iommu_dma_alloc_pages (count, alloc_sizes >>PAGE_SHIFT , gfp & ~__GFP_ZERO);if (!pages)return NULL ;
@@ -581,15 +581,18 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,if (sg_alloc_table_from_pages (&sgt, pages, count,0 , size,GFP_KERNEL )) goto out_free_iova; -if (!(prot &IOMMU_CACHE )) { + {struct sg_mapping_iter miter;/* * The CPU-centric flushing implied by SG_MITER_TO_SG isn't * sufficient here, so skip it by using the "wrong" direction. */ sg_miter_start (&miter, sgt.sgl, sgt.orig_nents,SG_MITER_FROM_SG ); -while (sg_miter_next (&miter)) +while (sg_miter_next (&miter)) { +clear_page (miter.addr); +if (!(prot &IOMMU_CACHE ))flush_page (dev, miter.addr,page_to_phys (miter.page)); + }sg_miter_stop (&miter); }
Hi Robin,
Quoted 8 lines · Robin Murphy
Quoted 8 lines
I re-ran the test to get some accuracy within the function and got:
1) pages = __iommu_dma_alloc_pages(count, alloc_sizes >> PAGE_SHIFT, gfp); // reduced from 422 usec to 56 usec == 366 usec less 2) if (!(prot & IOMMU_CACHE)) {...} //flush routine // reduced from 439 usec to 236 usec == 203 usec lessNote: new memset takes about 164 usec, resulting in 400 usec diff
for the entireiommu_dma_alloc()function call.
Quoted 8 lines
Quoted 2 lines
It looks like this might be more than the diff between
clear_pageand memset, and might be related to mapping and cache. Any idea?Hmm, I guess it might not be so much
clear_page()itself as all the gubbins involved in getting there fromprep_new_page(). I could perhaps make some vague guesses about how the A57 cores might get tickled by the different code patterns, but the Denver cores are well beyond my ability to reason about. Out of even further curiosity, how does the quick hack below compare?
I tried out that change. And the results are as followings:
a. Routine (1) reduced from 422 usec to 55 usec
b. Routine (2) increased from 441 usec to 833 usec c. Overall, it seems to remain the same: 900+ usec
Quoted 18 lines
Quoted 12 lines
Quoted 11 lines
Review struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, · line 598
Quoted 7 lines
(quoted diff) +4
-0@@ -581,6 +593,12 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp, if (sg_alloc_table_from_pages(&sgt, pages, count, 0, size, GFP_KERNEL)) goto out_free_iova; + if (gfp_zero) { + /* Now zero all the pages in the scatterlist */ + for_each_sg(sgt.sgl, s, sgt.orig_nents, i) + memset(sg_virt(s), 0, s->length);What if the pages came from highmem? I know that doesn't happen on arm64 today, but the point of this code *is* to be generic, and other users will arrive eventually.
Quoted 5 lines
Hmm, so it probably should use sg_miter_start/stop() too? Looking at the flush routine doing in
PAGE_SIZEfor each iteration, would be possible to map and memset contiguous pages together? Actually the flush routine might be also optimized if we can map contiguous pages.
Quoted 6 lines
I suppose the ideal point at which to do it would be after the remapping when we have the entire buffer contiguous in vmalloc space and can make best use of prefetchers etc. -
DMA_ATTR_NO_KERNEL_MAPPINGis a bit of a spanner in the works, but we could probably accommodate a special case for that. As Christoph points out, this isn't really the place to be looking for performance anyway (unless it's pathologically bad as per the
I would understand the point. So probably it'd be more plausible to have the change if it reflects on some practical benchmark. I might need to re-run some tests with heavier use cases.
Quoted 3 lines
DMA_ATTR_ALLOC_SINGLE_PAGESfun), but if we're looking at pulling the remapping out of the arch code, maybe we could aim to rework the zeroing completely as part of that.
That'd be nice. I believe it'd be good to have.
Thanks
Nicolin
Quoted 10 lines · Nicolin Chen
The
__GFP_ZEROwill be passed down to the generic page allocation routine which zeros everything page by page. This is safe to be a generic way but not efficient for iommu allocation that organizes contiguous pages using scatterlist.So this changes drops
__GFP_ZEROfrom the flag, and adds a manual memset after page/sg allocations, using the length of scatterlist.My test result of a 2.5MB size allocation shows
iommu_dma_alloc()takes 46% less time, reduced from averagely 925 usec to 500 usec.
And in what case does dma_alloc_* performance even matter?
Hi Christoph,
Quoted 6 lines · Christoph Hellwig
Quoted 5 lines · Nicolin Chen
The
__GFP_ZEROwill be passed down to the generic page allocation routine which zeros everything page by page. This is safe to be a generic way but not efficient for iommu allocation that organizes contiguous pages using scatterlist.
Quoted 7 lines
Quoted 5 lines
So this changes drops
__GFP_ZEROfrom the flag, and adds a manual memset after page/sg allocations, using the length of scatterlist.My test result of a 2.5MB size allocation shows
iommu_dma_alloc()takes 46% less time, reduced from averagely 925 usec to 500 usec.And in what case does dma_alloc_* performance even matter?
Honestly, this was amplified by running a local iommu benchmark test. Practically dma_alloc/free() should not be that stressful, but we cannot say the performance doesn't matter at all, right? Though many device drivers pre-allocte memory for DMA usage, it could matter where a driver dynamically allocates and releases.
And actually I have a related question for you: I saw that the dma_direct_alloc() cancels the __GFP_ZERO flag and does manual memset() after allocation. Might that be possibly related to a performance concern? Though I don't see any performance keyword for that part of code, especially seems that memset() was there from the beginning.
Thanks
Nicolin