Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1295222 > unrolled thread
| Started by | Douglas Anderson <dianders@chromium.org> |
|---|---|
| First post | 2015-12-18 23:30 +0100 |
| Last post | 2015-12-18 23:30 +0100 |
| Articles | 2 — 1 participant |
Back to article view | Back to linux.kernel
[PATCH v2 1/2] ARM: dma-mapping: Optimize allocation Douglas Anderson <dianders@chromium.org> - 2015-12-18 23:30 +0100
[PATCH v2 2/2] ARM: dma-mapping: sort the pages after allocation Douglas Anderson <dianders@chromium.org> - 2015-12-18 23:30 +0100
| From | Douglas Anderson <dianders@chromium.org> |
|---|---|
| Date | 2015-12-18 23:30 +0100 |
| Subject | [PATCH v2 1/2] ARM: dma-mapping: Optimize allocation |
| Message-ID | <qHbHb-3aG-7@gated-at.bofh.it> |
The __iommu_alloc_buffer() is expected to be called to allocate pretty
sizeable buffers. Upon simple tests of video I saw it trying to
allocate 4,194,304 bytes. The function tries to allocate large chunks
in order to optimize IOMMU TLB usage.
The current function is very, very slow.
One problem is the way it keeps trying and trying to allocate big
chunks. Imagine a very fragmented memory that has 4M free but no
contiguous pages at all. Further imagine allocating 4M (1024 pages).
We'll do the following memory allocations:
- For page 1:
- Try to allocate order 10 (no retry)
- Try to allocate order 9 (no retry)
- ...
- Try to allocate order 0 (with retry, but not needed)
- For page 2:
- Try to allocate order 9 (no retry)
- Try to allocate order 8 (no retry)
- ...
- Try to allocate order 0 (with retry, but not needed)
- ...
- ...
Total number of calls to alloc() calls for this case is:
sum(int(math.log(i, 2)) + 1 for i in range(1, 1025))
=> 9228
The above is obviously worse case, but given how slow alloc can be we
really want to try to avoid even somewhat bad cases. I timed the old
code with a device under memory pressure and it wasn't hard to see it
take more than 120 seconds to allocate 4 megs of memory! (NOTE: testing
was done on kernel 3.14, so possibly mainline would behave
differently).
A second problem is that allocating big chunks under memory pressure
when we don't need them is just not a great idea anyway unless we really
need them. We can make due pretty well with smaller chunks so it's
probably wise to leave bigger chunks for other users once memory
pressure is on.
Let's adjust the allocation like this:
1. If a big chunk fails, stop trying to hard and bump down to lower
order allocations.
2. Don't try useless orders. The whole point of big chunks is to
optimize the TLB and it can really only make use of 2M, 1M, 64K and
4K sizes.
We'll still tend to eat up a bunch of big chunks, but that might be the
right answer for some users. A future patch could possibly add a new
DMA_ATTR that would let the caller decide that TLB optimization isn't
important and that we should use smaller chunks. Presumably this would
be a sane strategy for some callers.
Signed-off-by: Douglas Anderson <dianders@chromium.org>
---
Changes in v2:
- No longer just 1 page at a time, but gives up higher order quickly.
- Only tries important higher order allocations that might help us.
arch/arm/mm/dma-mapping.c | 34 ++++++++++++++++++++--------------
1 file changed, 20 insertions(+), 14 deletions(-)
diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
index 492bf3efffab..9887d432cf1f 100644
--- a/arch/arm/mm/dma-mapping.c
+++ b/arch/arm/mm/dma-mapping.c
@@ -1122,6 +1122,9 @@ static inline void __free_iova(struct dma_iommu_mapping *mapping,
spin_unlock_irqrestore(&mapping->lock, flags);
}
+/* We'll try 2M, 1M, 64K, and finally 4K; array must end with 0! */
+static const int iommu_order_array[] = { 9, 8, 4, 0 };
+
static struct page **__iommu_alloc_buffer(struct device *dev, size_t size,
gfp_t gfp, struct dma_attrs *attrs)
{
@@ -1129,6 +1132,7 @@ static struct page **__iommu_alloc_buffer(struct device *dev, size_t size,
int count = size >> PAGE_SHIFT;
int array_size = count * sizeof(struct page *);
int i = 0;
+ int order_idx = 0;
if (array_size <= PAGE_SIZE)
pages = kzalloc(array_size, GFP_KERNEL);
@@ -1162,22 +1166,24 @@ static struct page **__iommu_alloc_buffer(struct device *dev, size_t size,
while (count) {
int j, order;
- for (order = __fls(count); order > 0; --order) {
- /*
- * We do not want OOM killer to be invoked as long
- * as we can fall back to single pages, so we force
- * __GFP_NORETRY for orders higher than zero.
- */
- pages[i] = alloc_pages(gfp | __GFP_NORETRY, order);
- if (pages[i])
- break;
+ order = iommu_order_array[order_idx];
+
+ /* Drop down when we get small */
+ if (__fls(count) < order) {
+ order_idx++;
+ continue;
}
- if (!pages[i]) {
- /*
- * Fall back to single page allocation.
- * Might invoke OOM killer as last resort.
- */
+ if (order) {
+ /* See if it's easy to allocate a high-order chunk */
+ pages[i] = alloc_pages(gfp | __GFP_NORETRY, order);
+
+ /* Go down a notch at first sign of pressure */
+ if (!pages[i]) {
+ order_idx++;
+ continue;
+ }
+ } else {
pages[i] = alloc_pages(gfp, 0);
if (!pages[i])
goto error;
--
2.6.0.rc2.230.g3dd15c0
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [next] | [standalone]
| From | Douglas Anderson <dianders@chromium.org> |
|---|---|
| Date | 2015-12-18 23:30 +0100 |
| Subject | [PATCH v2 2/2] ARM: dma-mapping: sort the pages after allocation |
| Message-ID | <qHbHb-3aG-11@gated-at.bofh.it> |
| In reply to | #1295222 |
After doing allocation, make one last-ditch effort to get contiguous
regions of pages to optimize TLB usage. This is a rather simplistic
approach that could be later optimized, but it doesn't hurt and should
only have the opportunity to help.
From my testing the sort took less than 400us for a 4MB allocation.
That's much faster than the actual allocation which was more than a
millisecond even in the fastest case (and was often several hundred ms).
Signed-off-by: Douglas Anderson <dianders@chromium.org>
---
Changes in v2:
- Sort patch new for v2 (and optional if people hate it).
arch/arm/mm/dma-mapping.c | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
index 9887d432cf1f..d1b3d3e6fe47 100644
--- a/arch/arm/mm/dma-mapping.c
+++ b/arch/arm/mm/dma-mapping.c
@@ -23,6 +23,7 @@
#include <linux/highmem.h>
#include <linux/memblock.h>
#include <linux/slab.h>
+#include <linux/sort.h>
#include <linux/iommu.h>
#include <linux/io.h>
#include <linux/vmalloc.h>
@@ -1122,6 +1123,21 @@ static inline void __free_iova(struct dma_iommu_mapping *mapping,
spin_unlock_irqrestore(&mapping->lock, flags);
}
+static int cmp_pfns(const void *a, const void *b)
+{
+ unsigned long a_pfn;
+ unsigned long b_pfn;
+
+ a_pfn = page_to_pfn(*(struct page **)a);
+ b_pfn = page_to_pfn(*(struct page **)b);
+
+ if (a_pfn < b_pfn)
+ return -1;
+ else if (a_pfn > b_pfn)
+ return 1;
+ return 0;
+}
+
/* We'll try 2M, 1M, 64K, and finally 4K; array must end with 0! */
static const int iommu_order_array[] = { 9, 8, 4, 0 };
@@ -1133,6 +1149,7 @@ static struct page **__iommu_alloc_buffer(struct device *dev, size_t size,
int array_size = count * sizeof(struct page *);
int i = 0;
int order_idx = 0;
+ int first_order_zero = -1;
if (array_size <= PAGE_SIZE)
pages = kzalloc(array_size, GFP_KERNEL);
@@ -1171,6 +1188,7 @@ static struct page **__iommu_alloc_buffer(struct device *dev, size_t size,
/* Drop down when we get small */
if (__fls(count) < order) {
order_idx++;
+ /* Don't update first_order_zero; no need to sort end */
continue;
}
@@ -1181,6 +1199,8 @@ static struct page **__iommu_alloc_buffer(struct device *dev, size_t size,
/* Go down a notch at first sign of pressure */
if (!pages[i]) {
order_idx++;
+ if (iommu_order_array[order_idx] == 0)
+ first_order_zero = i;
continue;
}
} else {
@@ -1201,6 +1221,26 @@ static struct page **__iommu_alloc_buffer(struct device *dev, size_t size,
count -= 1 << order;
}
+ /*
+ * If we folded under memory pressure, try one last ditch event to get
+ * contiguous pages via sorting. Under testing this sometimes helped
+ * get a few more contiguous pages and didn't cost much compared to
+ * the above allocations.
+ *
+ * Note that we only sort the order zero pages so that we don't mess
+ * up the higher order allocations by sticking small pages in between
+ * them.
+ *
+ * If someone wanted to optimize this more, they could insert extra
+ * (out of order) single pages in places to help keep virtual and
+ * physical pages aligned with each other. As it is we often get
+ * lucky and get the needed alignment but we're not guaranteed.
+ */
+ if (first_order_zero >= 0)
+ sort(pages + first_order_zero,
+ (size >> PAGE_SHIFT) - first_order_zero, sizeof(*pages),
+ cmp_pfns, NULL);
+
return pages;
error:
while (i--)
--
2.6.0.rc2.230.g3dd15c0
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web