Esempio n. 1
0
def test_name_policy_replace():
    slides = SlideDeck(name_policy="replace")
    slides.new_slide(name="xxx")
    slides.new_slide(name="yyy")
    b2 = slides.new_slide(name="xxx")

    s = slides._slides
    assert len(s) == 2
    assert s[0].name == "yyy"
    assert s[1].name == "xxx"
    assert s[1].box() is b2
Esempio n. 2
0
def test_cache2(test_env):
    cache_dir = "elsie-cache"

    slides = SlideDeck(name_policy="ignore")
    for i in range(10):
        slide = slides.new_slide()
        slide.box(width=100, height=100).rect(bg_color="blue")

    assert not glob.glob("{}/*".format(cache_dir))
    slides.render(output="test.pdf")

    files = set([os.path.basename(p) for p in glob.glob("{}/*".format(cache_dir))])
    assert 2 == len(files)

    slides = SlideDeck(name_policy="ignore")
    for i in range(10):
        slide = slides.new_slide()
        slide.box(width=100, height=100).rect(bg_color="green")

    slides.render(output="test.pdf")

    files2 = set([os.path.basename(p) for p in glob.glob("{}/*".format(cache_dir))])
    assert 2 == len(files2)
    assert files != files2
Esempio n. 3
0
def export_pptx(slide_deck: SlideDeck, filename: str, **render_args):
    import pptx

    pr = pptx.Presentation()

    for layout in pr.slide_layouts:
        if layout.name == "Blank":
            break
    else:
        raise Exception("Blank layout not found")

    images = slide_deck.render(None, export_type="png", **render_args)
    for image in images:
        slide = pr.slides.add_slide(layout)
        slide.shapes.add_picture(image, 0, 0, pr.slide_width, pr.slide_height)
    pr.save(filename)
Esempio n. 4
0
def new_slide(slides: SlideDeck):
    return slides.new_slide()
Esempio n. 5
0
def new_slides(width: int, height: int) -> SlideDeck:
    return SlideDeck(width=width, height=height)
Esempio n. 6
0
def test_name_policy_unique(policy):
    slides = SlideDeck(name_policy=policy)
    slides.new_slide()
    slides.new_slide()
    slides.new_slide(name="xxx")
    with pytest.raises(Exception, match="already exists"):
        slides.new_slide(name="xxx")
    slides.new_slide(name="yyy")
    with pytest.raises(Exception, match="has to be a string"):
        slides.new_slide(name=123)
    assert len(slides._slides) == 4
Esempio n. 7
0
def test_name_policy_ignore(policy):
    slides = SlideDeck(name_policy=policy)
    slides.new_slide(name="xxx")
    slides.new_slide(name="xxx")
    slides.new_slide(name="yyy")
    assert len(slides._slides) == 3
Esempio n. 8
0
def test_invalid_name_policy():
    with pytest.raises(Exception, match="Invalid"):
        SlideDeck(name_policy="xxx")
Esempio n. 9
0
def test_cache1(test_env):
    cache_dir = "elsie-cache"
    slides = SlideDeck(name_policy="ignore")

    slide = slides.new_slide()
    slide.box(show="1+").text("1")
    slide.box(show="next+").text("2")
    slide.box(show="next+").text("3")

    assert not glob.glob("{}/*".format(cache_dir))
    slides.render(output="test.pdf")

    print(glob.glob("{}/*".format(cache_dir)))

    fs = set(os.path.basename(p) for p in glob.glob("{}/*".format(cache_dir)))
    assert "queries3.cache" in fs
    assert len(fs) == 4

    slides = SlideDeck(name_policy="ignore")

    slide = slides.new_slide()
    slide.box(show="1+").text("1")
    slide.box(show="next+").text("2")
    slide.box(show="next+").text("3")

    slides.render(output="test.pdf")

    fs2 = set(os.path.basename(p) for p in glob.glob("{}/*".format(cache_dir)))
    assert fs2 == fs

    slides = SlideDeck(name_policy="ignore")

    slide = slides.new_slide()
    slide.box(show="1+").text("1")
    slide.box(show="next+").text("2")
    slide.box(show="next+").text("4")

    slides.render(output="test.pdf")

    print(fs2)
    fs3 = set(os.path.basename(p) for p in glob.glob("{}/*".format(cache_dir)))
    assert "queries3.cache" in fs3
    assert len(fs3.difference(fs)) == 1
Esempio n. 10
0
def cache_conflicts(slides: SlideDeck, backup: bool):
    if backup:
        slide = new_slide(slides)
        content = slide_header(slide, "Code (backup)")
        code(
            content.box(),
            """// Addresses of N integers, each `offset` bytes apart
std::vector<int*> data = ...;
for (auto ptr: data)
{
    *ptr += 1;
}
// Offsets: 4, 64, 4000, 4096, 4128""")
        slide = new_slide(slides)
        content = slide_header(slide, "Result (backup)")
        content.box(height=600).image("images/example1-time.png")

    slide = new_slide(slides)
    content = slide_header(slide, "Cache memory")
    content.box(height=600).image("images/haswell-diagram.png")
    content.box(width=230, height=40, x=816, y=530).rect(color=COLOR_BACKEND,
                                                         stroke_width=3)

    slide = new_slide(slides)
    content = slide_header(slide, "How are (L1) caches implemented")
    list_wrapper = content.box()
    list_item(list_wrapper).text("N-way set associative table")
    list_item(list_wrapper, level=1, show="last+").text("Hardware hash table")
    list_item(list_wrapper, show="next+").text("Key = address (8B)")
    list_item(list_wrapper, show="next+").text("Entry = cache line (64B)")

    slide = new_slide(slides)
    content = slide_header(slide, "N-way set associative cache")
    hash_size = 8
    hash_dimension = 60

    def table(wrapper: Box,
              size,
              dimension,
              buckets=None,
              bucket_indices=True):
        htable = wrapper.box(horizontal=True)
        items = []
        for i in range(size):
            cell = htable.box(width=dimension,
                              height=dimension,
                              horizontal=True).rect("black", stroke_width=2)
            items.append(cell)

        if buckets:
            bucket_width = int((size / buckets) * dimension)
            for i in range(buckets):
                pos = i * bucket_width
                htable.box(x=pos, y=0, width=bucket_width,
                           height=dimension).rect("black", stroke_width=6)
                if bucket_indices:
                    htable.box(x=pos, y=dimension - 5,
                               width=bucket_width).text(str(i))

        return (htable, items)

    content.box().text("Size = {} cache lines".format(hash_size),
                       style="notice")
    (htable, hitems) = table(content.box(p_top=20), hash_size, hash_dimension)
    arrow_wrapper = content.box()
    arrow = Arrow(20)
    arrow_wrapper.box().line([
        hitems[0].p("50%", 0).add(0, -20),
        hitems[-1].p("50%", 0).add(0, -20),
    ],
                             start_arrow=arrow,
                             end_arrow=arrow,
                             stroke_width=5,
                             color=COLOR_NOTE)

    content.box(
        p_top=20,
        show="next+").text("Associativity (N) - # of cache lines per bucket")
    content.box(p_top=10, show="next+").text("# of buckets = Size / N")

    row = content.box(horizontal=True, p_top=20)
    lcol = row.box(y=0)
    rcol = row.box(y=0, p_left=20)

    def htable_row(text, block_count):
        padding = 20
        height = 110
        lcol.box(show="next+", p_top=padding, height=height).text(text)
        return table(rcol.box(show="last+", p_top=padding, height=height),
                     hash_size, hash_dimension, block_count)

    htable_row("N = 1 (direct mapped)", hash_size)
    htable_row("N = {} (fully associative)".format(hash_size), 1)
    htable_row("N = 2", hash_size // 2)

    slide = new_slide(slides)
    content = slide_header(slide, "How are addresses hashed?")

    content.box().text("64-bit address:")
    row = content.box(horizontal=True, width=800)
    widths = ["60%", "25%", "15%"]
    address_colors = ["#B22222", "#007944", "#0018AE"]
    labels = ["Tag", "Index", "Offset"]

    for i in range(3):
        wrapper = row.box(width=widths[i]).rect(color=address_colors[i],
                                                stroke_width=4)
        wrapper.box(padding=4).text(labels[i])
    labelrow = content.box(horizontal=True, x=220)
    labelrow.box().text("63")
    labelrow.box(p_left=770).text("0")

    list_wrapper = content.box(p_top=20)
    list_item(list_wrapper, show="next+").text("Offset", "bold")
    list_item(list_wrapper, level=1,
              show="last+").text("Selects byte within a cache line")
    list_item(list_wrapper, level=1,
              show="last+").text("log2(cache line size) bits")
    list_item(list_wrapper, show="next+").text("Index", "bold")
    list_item(list_wrapper, level=1,
              show="last+").text("Selects bucket within the cache")
    list_item(list_wrapper, level=1,
              show="last+").text("log2(bucket count) bits")
    list_item(list_wrapper, show="next+").text("Tag", "bold")
    list_item(list_wrapper, level=1, show="last+").text("Used for matching")

    slide = new_slide(slides)
    content = slide_header(slide, "N-way set associative cache")

    queue = content.box(x="55%", y=40, horizontal=True)
    queue.box(p_right=40).text("Cache lines:")
    colors = ("#F0134D", "#FF6F5E", "#F0134D")
    cacheline_labels = ("A", "B", "C")
    for i in range(3):
        queue.box(width=hash_dimension,
                  height=hash_dimension).rect(color="black",
                                              bg_color=colors[i],
                                              stroke_width=5).text(
                                                  cacheline_labels[i],
                                                  style=s(bold=True,
                                                          color="white"))

    index = content.box(x="55%", y=90, horizontal=True)
    index.box(p_right=75).text("Index bits:")
    index_bits = (0, 1, 0)
    for i in range(3):
        index.box(width=hash_dimension,
                  height=hash_dimension).text(str(index_bits[i]))

    def insert(slot, show, item):
        wrapper = slot.overlay(show=show)
        wrapper.rect(bg_color=colors[item])
        wrapper.text(cacheline_labels[item], style=s(bold=True, color="white"))

    row = content.box(horizontal=True, p_top=20)
    lcol = row.box(y=0)
    rcol = row.box(y=0, p_left=20)

    def htable_row(text, block_count):
        padding = 20
        height = 140
        lcol.box(show="next+", p_top=padding, height=height).text(text)
        return table(rcol.box(show="last+", p_top=padding, height=height),
                     hash_size, hash_dimension, block_count)

    (_, hitems) = htable_row("N = 1", hash_size)
    insert(hitems[0], "next+", 0)
    insert(hitems[1], "next+", 1)
    insert(hitems[0], "next+", 2)

    (_, hitems) = htable_row("N = {}".format(hash_size), 1)
    insert(hitems[0], "next+", 0)
    insert(hitems[1], "next+", 1)
    insert(hitems[2], "next+", 2)

    (_, hitems) = htable_row("N = 2", hash_size // 2)
    insert(hitems[0], "next+", 0)
    insert(hitems[2], "next+", 1)
    insert(hitems[1], "next+", 2)

    slide = new_slide(slides)
    slide.update_style("default", s(size=46))
    slide.update_style("bold", s(size=46))
    content = slide_header(slide, "Intel L1 cache")
    bash(content.box(),
         """$ getconf -a | grep LEVEL1_DCACHE
LEVEL1_DCACHE_SIZE      32768
LEVEL1_DCACHE_ASSOC     8
LEVEL1_DCACHE_LINESIZE  64""",
         text_style=s(align="left"))
    list_wrapper = content.box(p_top=20)
    list_item(
        list_wrapper,
        show="next+").text("~bold{Cache line size} - 64 B (6 offset bits)")
    list_item(list_wrapper, show="next+").text("~bold{Associativity} (N) - 8")
    list_item(list_wrapper, show="next+").text("~bold{Size} - 32768 B")
    list_item(list_wrapper, show="next+").text("32768 / 64 => 512 cache lines")
    list_item(list_wrapper,
              show="next+").text("512 / 8 => 64 buckets (6 index bits)")

    slides.set_style("tag", s(color=address_colors[0]))
    tag = slides.get_style("tag")
    slides.set_style("index", tag.compose(s(color=address_colors[1])))
    slides.set_style("offset", tag.compose(s(color=address_colors[2])))

    styles = ["tag", "index", "offset"]
    colors = ["#F0134D", "#FF6F5E", "#1F6650", "#40BFC1"]

    def address(cols, content, next=True, use_style=True, row=0):
        for i, col in enumerate(cols):
            show = "1+"
            if next:
                show = "next+" if i == 0 else "last+"

            style = "default"
            if use_style:
                if i == 0:
                    style = s(color=colors[row])
                else:
                    style = styles[i - 1]
            col.box(show=show).text(content[i], style=style)

    slide = new_slide(slides)
    content = slide_header(slide, "Offset = 4B")

    width = 700
    columns = 4
    row = content.box(horizontal=True)
    cols = [row.box(width=width // columns) for _ in range(columns)]

    address(cols, ("Number", "Tag", "Index", "Offset"),
            next=False,
            use_style=False)
    address(cols, ("A", "..100000", "000000", "000000"), next=False)
    address(cols, ("B", "..100000", "000000", "000100"), row=1)
    address(cols, ("C", "..100000", "000000", "001000"), row=2)
    address(cols, ("D", "..100000", "000000", "001100"), row=3)

    hash_dimension = 80
    (htable, hitems) = table(content.box(p_top=20), hash_size, hash_dimension,
                             hash_size // 2)
    for i in range(4):
        hitems[0].box(show="{}+".format(i + 1),
                      width=hash_dimension // 4,
                      height=hash_dimension).rect(bg_color=colors[i])

    list_wrapper = content.box(p_top=40)
    list_item(
        list_wrapper,
        show="next+").text("Same bucket, same cache line for each number")
    list_item(list_wrapper,
              show="next+").text("Most efficient, no space is wasted")

    slide = new_slide(slides)
    content = slide_header(slide, "Offset = 64B")

    row = content.box(horizontal=True)
    cols = [row.box(width=width // columns) for _ in range(columns)]

    address(cols, ("Number", "Tag", "Index", "Offset"),
            next=False,
            use_style=False)
    address(cols, ("A", "..100000", "000000", "000000"), next=False)
    address(cols, ("B", "..100000", "000001", "000000"), row=1)
    address(cols, ("C", "..100000", "000010", "000000"), row=2)
    address(cols, ("D", "..100000", "000011", "000000"), row=3)

    (htable, hitems) = table(content.box(p_top=20), hash_size, hash_dimension,
                             hash_size // 2)
    for i in range(4):
        hitems[i * 2].box(show="{}+".format(i + 1),
                          width=hash_dimension // 4,
                          height=hash_dimension,
                          x=0).rect(bg_color=colors[i])

    list_wrapper = content.box(p_top=40)
    list_item(list_wrapper,
              show="next+").text("Different bucket for each number")
    list_item(list_wrapper, show="next+").text("Wastes 60B in each cache line")
    list_item(list_wrapper,
              show="next+").text("Equally distributed among buckets")

    slide = new_slide(slides)
    content = slide_header(slide, "Offset = 4096B")

    row = content.box(horizontal=True)
    cols = [row.box(width=width // columns) for _ in range(columns)]

    address(cols, ("Number", "Tag", "Index", "Offset"),
            next=False,
            use_style=False)
    address(cols, ("A", "..100000", "000000", "000000"), next=False)
    address(cols, ("B", "..100001", "000000", "000000"), row=1)
    address(cols, ("C", "..100010", "000000", "000000"), row=2)
    address(cols, ("D", "..100011", "000000", "000000"), row=3)

    (htable, hitems) = table(content.box(p_top=20), hash_size, hash_dimension,
                             hash_size // 2)
    for i in range(4):
        hitems[i % 2].box(show="{}+".format(i + 1),
                          width=hash_dimension // 4,
                          height=hash_dimension,
                          x=0).rect(bg_color=colors[i])

    list_wrapper = content.box(p_top=40)
    list_item(list_wrapper, show="next+").text(
        "Same bucket, but different cache lines for each number!")
    list_item(list_wrapper,
              show="next+").text("Bucket full => evictions necessary")

    slide = new_slide(slides)
    content = slide_header(slide, "How to measure?")
    content.box().text("~tt{l1d.replacement}", style=s(size=48))
    content.box(
        p_top=20).text("How many times was a cache line loaded into L1?")

    if backup:
        bash(content.box(p_top=40, show="next+"),
             """$ perf stat -e l1d.replacement ./example1
4B    offset ->     149 558
4096B offset -> 426 218 383""",
             text_style=s(align="left"))