Skip to content

Commit 5b65c46

Browse files
kirylIngo Molnar
authored andcommitted
mm, x86/mm: Fix performance regression in get_user_pages_fast()
The 0-day test bot found a performance regression that was tracked down to switching x86 to the generic get_user_pages_fast() implementation: http://lkml.kernel.org/r/20170710024020.GA26389@yexl-desktop The regression was caused by the fact that we now use local_irq_save() + local_irq_restore() in get_user_pages_fast() to disable interrupts. In x86 implementation local_irq_disable() + local_irq_enable() was used. The fix is to make get_user_pages_fast() use local_irq_disable(), leaving local_irq_save() for __get_user_pages_fast() that can be called with interrupts disabled. Numbers for pinning a gigabyte of memory, one page a time, 20 repeats: Before: Average: 14.91 ms, stddev: 0.45 ms After: Average: 10.76 ms, stddev: 0.18 ms Signed-off-by: Kirill A. Shutemov <[email protected]> Cc: Andrew Morton <[email protected]> Cc: Huang Ying <[email protected]> Cc: Jonathan Corbet <[email protected]> Cc: Linus Torvalds <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: Thorsten Leemhuis <[email protected]> Cc: [email protected] Fixes: e585513 ("x86/mm/gup: Switch GUP to the generic get_user_page_fast() implementation") Link: http://lkml.kernel.org/r/[email protected] Signed-off-by: Ingo Molnar <[email protected]>
1 parent 9e52fc2 commit 5b65c46

File tree

1 file changed

+58
-39
lines changed

1 file changed

+58
-39
lines changed

mm/gup.c

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,17 +1618,56 @@ static int gup_p4d_range(pgd_t pgd, unsigned long addr, unsigned long end,
16181618
return 1;
16191619
}
16201620

1621+
static void gup_pgd_range(unsigned long addr, unsigned long end,
1622+
int write, struct page **pages, int *nr)
1623+
{
1624+
unsigned long next;
1625+
pgd_t *pgdp;
1626+
1627+
pgdp = pgd_offset(current->mm, addr);
1628+
do {
1629+
pgd_t pgd = READ_ONCE(*pgdp);
1630+
1631+
next = pgd_addr_end(addr, end);
1632+
if (pgd_none(pgd))
1633+
return;
1634+
if (unlikely(pgd_huge(pgd))) {
1635+
if (!gup_huge_pgd(pgd, pgdp, addr, next, write,
1636+
pages, nr))
1637+
return;
1638+
} else if (unlikely(is_hugepd(__hugepd(pgd_val(pgd))))) {
1639+
if (!gup_huge_pd(__hugepd(pgd_val(pgd)), addr,
1640+
PGDIR_SHIFT, next, write, pages, nr))
1641+
return;
1642+
} else if (!gup_p4d_range(pgd, addr, next, write, pages, nr))
1643+
return;
1644+
} while (pgdp++, addr = next, addr != end);
1645+
}
1646+
1647+
#ifndef gup_fast_permitted
1648+
/*
1649+
* Check if it's allowed to use __get_user_pages_fast() for the range, or
1650+
* we need to fall back to the slow version:
1651+
*/
1652+
bool gup_fast_permitted(unsigned long start, int nr_pages, int write)
1653+
{
1654+
unsigned long len, end;
1655+
1656+
len = (unsigned long) nr_pages << PAGE_SHIFT;
1657+
end = start + len;
1658+
return end >= start;
1659+
}
1660+
#endif
1661+
16211662
/*
16221663
* Like get_user_pages_fast() except it's IRQ-safe in that it won't fall back to
16231664
* the regular GUP. It will only return non-negative values.
16241665
*/
16251666
int __get_user_pages_fast(unsigned long start, int nr_pages, int write,
16261667
struct page **pages)
16271668
{
1628-
struct mm_struct *mm = current->mm;
16291669
unsigned long addr, len, end;
1630-
unsigned long next, flags;
1631-
pgd_t *pgdp;
1670+
unsigned long flags;
16321671
int nr = 0;
16331672

16341673
start &= PAGE_MASK;
@@ -1652,45 +1691,15 @@ int __get_user_pages_fast(unsigned long start, int nr_pages, int write,
16521691
* block IPIs that come from THPs splitting.
16531692
*/
16541693

1655-
local_irq_save(flags);
1656-
pgdp = pgd_offset(mm, addr);
1657-
do {
1658-
pgd_t pgd = READ_ONCE(*pgdp);
1659-
1660-
next = pgd_addr_end(addr, end);
1661-
if (pgd_none(pgd))
1662-
break;
1663-
if (unlikely(pgd_huge(pgd))) {
1664-
if (!gup_huge_pgd(pgd, pgdp, addr, next, write,
1665-
pages, &nr))
1666-
break;
1667-
} else if (unlikely(is_hugepd(__hugepd(pgd_val(pgd))))) {
1668-
if (!gup_huge_pd(__hugepd(pgd_val(pgd)), addr,
1669-
PGDIR_SHIFT, next, write, pages, &nr))
1670-
break;
1671-
} else if (!gup_p4d_range(pgd, addr, next, write, pages, &nr))
1672-
break;
1673-
} while (pgdp++, addr = next, addr != end);
1674-
local_irq_restore(flags);
1694+
if (gup_fast_permitted(start, nr_pages, write)) {
1695+
local_irq_save(flags);
1696+
gup_pgd_range(addr, end, write, pages, &nr);
1697+
local_irq_restore(flags);
1698+
}
16751699

16761700
return nr;
16771701
}
16781702

1679-
#ifndef gup_fast_permitted
1680-
/*
1681-
* Check if it's allowed to use __get_user_pages_fast() for the range, or
1682-
* we need to fall back to the slow version:
1683-
*/
1684-
bool gup_fast_permitted(unsigned long start, int nr_pages, int write)
1685-
{
1686-
unsigned long len, end;
1687-
1688-
len = (unsigned long) nr_pages << PAGE_SHIFT;
1689-
end = start + len;
1690-
return end >= start;
1691-
}
1692-
#endif
1693-
16941703
/**
16951704
* get_user_pages_fast() - pin user pages in memory
16961705
* @start: starting user address
@@ -1710,12 +1719,22 @@ bool gup_fast_permitted(unsigned long start, int nr_pages, int write)
17101719
int get_user_pages_fast(unsigned long start, int nr_pages, int write,
17111720
struct page **pages)
17121721
{
1722+
unsigned long addr, len, end;
17131723
int nr = 0, ret = 0;
17141724

17151725
start &= PAGE_MASK;
1726+
addr = start;
1727+
len = (unsigned long) nr_pages << PAGE_SHIFT;
1728+
end = start + len;
1729+
1730+
if (unlikely(!access_ok(write ? VERIFY_WRITE : VERIFY_READ,
1731+
(void __user *)start, len)))
1732+
return 0;
17161733

17171734
if (gup_fast_permitted(start, nr_pages, write)) {
1718-
nr = __get_user_pages_fast(start, nr_pages, write, pages);
1735+
local_irq_disable();
1736+
gup_pgd_range(addr, end, write, pages, &nr);
1737+
local_irq_enable();
17191738
ret = nr;
17201739
}
17211740

0 commit comments

Comments
 (0)