IT 251 Computer Organization and Architecture

Slides:



Advertisements
Similar presentations
Lecture 8: Memory Hierarchy Cache Performance Kai Bu
Advertisements

Lecture 12 Reduce Miss Penalty and Hit Time
Performance of Cache Memory
Practical Caches COMP25212 cache 3. Learning Objectives To understand: –Additional Control Bits in Cache Lines –Cache Line Size Tradeoffs –Separate I&D.
1 Adapted from UCB CS252 S01, Revised by Zhao Zhang in IASTATE CPRE 585, 2004 Lecture 14: Hardware Approaches for Cache Optimizations Cache performance.
331 Lec20.1Fall :332:331 Computer Architecture and Assembly Language Fall 2003 Week 13 Basics of Cache [Adapted from Dave Patterson’s UCB CS152.
EENG449b/Savvides Lec /13/04 April 13, 2004 Prof. Andreas Savvides Spring EENG 449bG/CPSC 439bG Computer.
ENEE350 Ankur Srivastava University of Maryland, College Park Based on Slides from Mary Jane Irwin ( )
1  Caches load multiple bytes per block to take advantage of spatial locality  If cache block size = 2 n bytes, conceptually split memory into 2 n -byte.
1 COMP 206: Computer Architecture and Implementation Montek Singh Wed, Nov 9, 2005 Topic: Caches (contd.)
331 Lec20.1Spring :332:331 Computer Architecture and Assembly Language Spring 2005 Week 13 Basics of Cache [Adapted from Dave Patterson’s UCB CS152.
Reducing Cache Misses 5.1 Introduction 5.2 The ABCs of Caches 5.3 Reducing Cache Misses 5.4 Reducing Cache Miss Penalty 5.5 Reducing Hit Time 5.6 Main.
1 Block replacement  Any empty block in the correct set may be used for storing data.  If there are no empty blocks, the cache controller will attempt.
 Higher associativity means more complex hardware  But a highly-associative cache will also exhibit a lower miss rate —Each set has more blocks, so there’s.
1 Caches Concepts Review  What is a block address? —Why not bring just what is needed by the processor?  What is a set associative cache?  Write-through?
CMPE 421 Parallel Computer Architecture
Lecture 10 Memory Hierarchy and Cache Design Computer Architecture COE 501.
The Memory Hierarchy 21/05/2009Lecture 32_CA&O_Engr Umbreen Sabir.
How to Build a CPU Cache COMP25212 – Lecture 2. Learning Objectives To understand: –how cache is logically structured –how cache operates CPU reads CPU.
CSIE30300 Computer Architecture Unit 08: Cache Hsin-Chou Chi [Adapted from material by and
1 How will execution time grow with SIZE? int array[SIZE]; int sum = 0; for (int i = 0 ; i < ; ++ i) { for (int j = 0 ; j < SIZE ; ++ j) { sum +=
Caches Where is a block placed in a cache? –Three possible answers  three different types AnywhereFully associativeOnly into one block Direct mappedInto.
Lecture 08: Memory Hierarchy Cache Performance Kai Bu
1 Chapter Seven. 2 Users want large and fast memories! SRAM access times are ns at cost of $100 to $250 per Mbyte. DRAM access times are ns.
COMP SYSTEM ARCHITECTURE HOW TO BUILD A CACHE Antoniu Pop COMP25212 – Lecture 2Jan/Feb 2015.
January 4, 2016©2003 Craig Zilles (derived from slides by Howard Huang) 1 Cache Writing & Performance  Today we’ll finish up with caches; we’ll cover:
DECStation 3100 Block Instruction Data Effective Program Size Miss Rate Miss Rate Miss Rate 1 6.1% 2.1% 5.4% 4 2.0% 1.7% 1.9% 1 1.2% 1.3% 1.2% 4 0.3%
CS.305 Computer Architecture Memory: Caches Adapted from Computer Organization and Design, Patterson & Hennessy, © 2005, and from slides kindly made available.
CPE232 Cache Introduction1 CPE 232 Computer Organization Spring 2006 Cache Introduction Dr. Gheith Abandah [Adapted from the slides of Professor Mary Irwin.
1 Chapter Seven. 2 Users want large and fast memories! SRAM access times are ns at cost of $100 to $250 per Mbyte. DRAM access times are ns.
COMP SYSTEM ARCHITECTURE PRACTICAL CACHES Sergio Davies Feb/Mar 2014COMP25212 – Lecture 3.
The Memory Hierarchy (Lectures #17 - #20) ECE 445 – Computer Organization The slides included herein were taken from the materials accompanying Computer.
1 How will execution time grow with SIZE? int array[SIZE]; int sum = 0; for (int i = 0 ; i < ; ++ i) { for (int j = 0 ; j < SIZE ; ++ j) { sum +=
Memory Hierarchy— Five Ways to Reduce Miss Penalty.
Cache Writing & Performance
CSE 351 Section 9 3/1/12.
Cache Memory and Performance
Yu-Lun Kuo Computer Sciences and Information Engineering
Improving Memory Access 1/3 The Cache and Virtual Memory
CSC 4250 Computer Architectures
How will execution time grow with SIZE?
Today How’s Lab 3 going? HW 3 will be out today
5.2 Eleven Advanced Optimizations of Cache Performance
Cache Memory Presentation I
Morgan Kaufmann Publishers Memory & Cache
William Stallings Computer Organization and Architecture 7th Edition
Cache memory Direct Cache Memory Associate Cache Memory
ECE 445 – Computer Organization
Cache Memories September 30, 2008
Lecture 08: Memory Hierarchy Cache Performance
Memory Hierarchy Memory: hierarchy of components of various speeds and capacities Hierarchy driven by cost and performance In early days Primary memory.
Performance metrics for caches
Performance metrics for caches
Adapted from slides by Sally McKee Cornell University
ECE232: Hardware Organization and Design
Performance metrics for caches
Morgan Kaufmann Publishers
How can we find data in the cache?
Miss Rate versus Block Size
Morgan Kaufmann Publishers Memory Hierarchy: Cache Basics
EE108B Review Session #6 Daxia Ge Friday February 23rd, 2007
CS-447– Computer Architecture Lecture 20 Cache Memories
CS 3410, Spring 2014 Computer Science Cornell University
Memory Hierarchy Memory: hierarchy of components of various speeds and capacities Hierarchy driven by cost and performance In early days Primary memory.
Performance metrics for caches
Chapter Five Large and Fast: Exploiting Memory Hierarchy
Cache Writing & Performance
Performance metrics for caches
10/18: Lecture Topics Using spatial locality
Overview Problem Solution CPU vs Memory performance imbalance
Presentation transcript:

IT 251 Computer Organization and Architecture Cache Performance Chia-Chi Teng

Summary of What We Saw So Far Larger block sizes can take advantage of spatial locality by loading data from not just one address, but also nearby addresses, into the cache. Associative caches assign each memory address to a particular set within the cache, but not to any specific block within that set. Set sizes range from 1 (direct-mapped) to 2k (fully associative). Larger sets and higher associativity lead to fewer cache conflicts and lower miss rates, but they also increase the hardware cost. In practice, 2-way through 16-way set-associative caches strike a good balance between lower miss rates and higher costs.

Cache Writing & Performance We’ll now cover: Writing to caches: keeping memory consistent & write-allocation. We’ll try to quantify the benefits of different cache designs, and see how caches affect overall performance. We’ll also investigate some main memory organizations that can help increase memory system performance. Next two lectures, we’ll talk about Virtual Memory, where memory is treated like a cache of the disk.

Four important questions 1. When we copy a block of data from main memory to the cache, where exactly should we put it? 2. How can we tell if a word is already in the cache (hit), or if it has to be fetched from main memory first (miss)? 3. Eventually, the small cache memory might fill up. To load a new block from main RAM, we’d have to replace one of the existing blocks in the cache... which one? 4. How can write operations be handled by the memory system? 1. When we copy a block of data from main memory to the cache, where exactly should we put it? 2. How can we tell if a word is already in the cache, or if it has to be fetched from main memory first? 3. Eventually, the small cache memory might fill up. To load a new block from main RAM, we’d have to replace one of the existing blocks in the cache... which one? 4. How can write operations be handled by the memory system? 1. When we copy a block of data from main memory to the cache, where exactly should we put it? 2. How can we tell if a word is already in the cache, or if it has to be fetched from main memory first? 3. Eventually, the small cache memory might fill up. To load a new block from main RAM, we’d have to replace one of the existing blocks in the cache... which one? 4. How can write operations be handled by the memory system? Previous lectures answered the first 3. Today, we consider the 4th.

Writing to a cache Writing to a cache raises several additional issues. First, let’s assume that the address we want to write to is already loaded in the cache. We’ll assume a simple direct-mapped cache. If we write a new value to that address, we can store the new data in the cache, and avoid an expensive main memory access. Index Tag Data V Address ... 110 1 11010 42803 1101 0110 Index Tag Data V Address ... 110 1 11010 21763 42803 1101 0110 Mem[214] = 21763

Inconsistent memory But now the cache and memory contain different, inconsistent data! How can we ensure that subsequent loads will return the right value? This is also problematic if other devices are sharing the main memory, as in a multiprocessor system. Index Tag Data V Address ... 110 1 11010 21763 42803 1101 0110

Write-through caches A write-through cache solves the inconsistency problem by forcing all writes to update both the cache and the main memory. This is simple to implement and keeps the cache and memory consistent. Why is this not so good? Index Tag Data V Address ... 110 1 11010 21763 1101 0110 Mem[214] = 21763

Write-through caches A write-through cache solves the inconsistency problem by forcing all writes to update both the cache and the main memory. This is simple to implement and keeps the cache and memory consistent. SLOW! The bad thing is that forcing every write to go to main memory, we use up bandwidth between the cache and the memory. Index Tag Data V Address ... 110 1 11010 21763 1101 0110 Mem[214] = 21763

Write buffers Write-through caches can result in slow writes, so processors typically include a write buffer, which queues pending writes to main memory and permits the CPU to continue. Buffers are commonly used when two devices run at different speeds. If a producer generates data too quickly for a consumer to handle, the extra data is stored in a buffer and the producer can continue on with other tasks, without waiting for the consumer. Conversely, if the producer slows down, the consumer can continue running at full speed as long as there is excess data in the buffer. For us, the producer is the CPU and the consumer is the main memory. Buffer Producer Consumer

Write-back caches (Buffers) In a write-back cache, the memory is not updated until the cache block needs to be replaced (e.g., when loading data into a full cache set). For example, we might write some data to the cache at first, leaving it inconsistent with the main memory as shown before. The cache block is marked “dirty” to indicate this inconsistency Subsequent reads to the same memory address will be serviced by the cache, which contains the correct, updated data. When is this good? Example? Mem[214] = 21763 Index V Dirty Tag Data Address Data ... 110 1000 1110 1101 0110 ... 1225 1 1 11010 21763 42803

Temporal locality in data Programs often access the same variables over and over, especially within loops. Below, sum and i are repeatedly read and written. sum = 0; for (i = 0; i < MAX; i++) sum = sum + f(i); 11

Finishing the write back We don’t need to store the new value back to main memory unless the cache block gets replaced. For example, on a read from Mem[142], which maps to the same cache block, the modified cache contents will first be written to main memory. Only then can the cache block be replaced with data from address 142. Index V 1 Dirty 1 Tag Data Address Data ... 110 1000 1110 ... 1101 0110 1225 11010 21763 21763 Index Tag Data Address Data ... 110 1000 1110 ... 1101 0110 1225 10001 1225 21763

Write-back cache discussion Each block in a write-back cache needs a dirty bit to indicate whether or not it must be saved to main memory before being replaced—otherwise we might perform unnecessary writebacks. Notice the penalty for the main memory access will not be applied until the execution of some subsequent instruction following the write. In our example, the write to Mem[214] affected only the cache. But the load from Mem[142] resulted in two memory accesses: one to save data to address 214, and one to load data from address 142. The write can be “buffered” as was shown in write-through. The advantage of write-back caches is that not all write operations need to access main memory, as with write-through caches. If a single address is frequently written to, then it doesn’t pay to keep writing that data through to main memory. If several bytes within the same cache block are modified, they will only force one memory write operation at write-back time.

Write misses A second scenario is if we try to write to an address that is not already contained in the cache; this is called a write miss. Let’s say we want to store 21763 into Mem[1101 0110] but we find that address is not currently in the cache. When we update Mem[1101 0110], should we also load it into the cache? Index Tag Data V Address ... 110 1 00010 123456 6378 1101 0110

Write around caches (a.k.a. write-no-allocate) With a write around policy, the write operation goes directly to main memory without affecting the cache. When is this good? Example? Address Data 21763 ... 1101 0110 Mem[214] = 21763 Index Tag Data V ... 110 1 00010 123456

Write around caches (a.k.a. write-no-allocate) With a write around policy, the write operation goes directly to main memory without affecting the cache. This is good when data is written but not immediately used again, in which case there’s no point to load it into the cache yet. for (int i = 0; i < SIZE; i++) a[i] = i; Address Data 21763 ... 1101 0110 Mem[214] = 21763 Index Tag Data V ... 110 1 00010 123456

Allocate on write An allocate on write strategy would instead load the newly written data into the cache. If that data is needed again soon, it will be available in the cache. for (int i = 0; i < SIZE; i++) a[i] = i; Index Tag Data V Address ... 110 1 11010 21763 1101 0110 Mem[214] = 21763

Which is it? Given the following trace of accesses, can you determine whether the cache is write-allocate or write-no-allocate? Assume A and B are distinct, and can be in the cache simultaneously. Miss Load A Miss Store B Hit Store A Hit Load A The 3rd miss is the key. If the cache was write-allocate, it would have been a hit. Miss Load B Hit Load B Hit Load A

Which is it? Given the following trace of accesses, can you determine whether the cache is write-allocate or write-no-allocate? Assume A and B are distinct, and can be in the cache simultaneously. Miss Load A Miss Store B Hit Store A Hit Load A The 3rd miss is the key. If the cache was write-allocate, it would have been a hit. Miss Load B Hit Load B Hit Load A On a write-allocate cache this would be a hit Answer: Write-no-allocate

First Observations Split Instruction/Data caches: Pro: No structural hazard between IF & MEM stages A single-ported unified cache stalls fetch during load or store Con: Static partitioning of cache between instructions & data Bad if working sets unequal: e.g., code/DATA or CODE/data Cache Hierarchies: Trade-off between access time & hit rate L1 cache can focus on fast access time (okay hit rate) L2 cache can focus on good hit rate (okay access time) Such hierarchical design is another “big idea” We’ll see this in section. L1 cache CPU Main Memory L2 cache

Opteron (Single Core) Vital Statistics L1 Caches: Instruction & Data 64 kB 64 byte blocks 2-way set associative 2 cycle access time L2 Cache: 1 MB 16-way set associative 11 cycle access time Memory 200+ cycle access time Quad-core Opteron: L1 cache: 64 + 64 kB L2 cache: 512 kB per core L3 cache: 2048 kB shared L1 cache CPU Main Memory L2 cache

Comparing cache organizations Like many architectural features, caches are evaluated experimentally. As always, performance depends on the actual instruction mix, since different programs will have different memory access patterns. Simulating or executing real applications is the most accurate way to measure performance characteristics. The graphs on the next few slides illustrate the simulated miss rates for several different cache designs. Again lower miss rates are generally better, but remember that the miss rate is just one component of average memory access time and execution time.

Associativity tradeoffs and miss rates As we saw last time, higher associativity means more complex hardware. But a highly-associative cache will also exhibit a lower miss rate. Each set has more blocks, so there’s less chance of a conflict between two addresses which both belong in the same set. Overall, this will reduce AMAT and memory stall cycles. The textbook shows the miss rates decreasing as the associativity increases. 0% 3% 6% 9% 12% Eight-way Four-way Two-way One-way Miss rate Associativity

Cache size and miss rates The cache size also has a significant impact on performance. The larger a cache is, the less chance there will be of a conflict. Again this means the miss rate decreases, so the AMAT and number of memory stall cycles also decrease. This figure depicts the miss rate as a function of both the cache size and its associativity. 0% 3% 6% 9% 12% 15% Eight-way Four-way Two-way One-way 1 KB 2 KB 4 KB 8 KB Miss rate Associativity

Block size and miss rates Finally, this figure shows miss rates relative to the block size and overall cache size. Smaller blocks do not take maximum advantage of spatial locality. But if blocks are too large, there will be fewer blocks available, and more potential misses due to conflicts. 1 KB 8 KB 16 KB 64 KB 256 40% 35% 30% 25% 20% 15% 10% 5% 0% Miss rate 64 16 4 Block size (bytes)

Memory and overall performance How do cache hits and misses affect overall system performance? Assuming a hit time of one CPU clock cycle, program execution will continue normally on a cache hit. (Our earlier computations always assumed one clock cycle for an instruction fetch or data access.) For cache misses, we’ll assume the CPU must stall to wait for a load from main memory. The total number of stall cycles depends on the number of cache misses and the miss penalty. Memory stall cycles = # Memory accesses x miss rate x miss penalty To include stalls due to cache misses in CPU performance equations, we have to add them to the “base” number of execution cycles. CPU time = (CPU execution cycles + Memory stall cycles) x Cycle time

Performance example Assume that 33% of the I number of instructions in a program are data accesses. The cache hit ratio is 97% and the hit time is one cycle, but the miss penalty is 20 cycles. Memory stall cycles = # Memory accesses x miss rate x miss penalty = ?

Performance example Assume that 33% of the I number of instructions in a program are data accesses. The cache hit ratio is 97% and the hit time is one cycle, but the miss penalty is 20 cycles. Memory stall cycles = # Memory accesses x miss rate x miss penalty = 0.33 I x 0.03 x 20 cycles = 0.2 I cycles If I instructions are executed, then the number of wasted cycles will be 0.2 x I. This code is 1.2 times slower than a program with a “perfect” CPI of 1!

Memory systems are a bottleneck CPU time = (CPU execution cycles + Memory stall cycles) x Cycle time Processor performance traditionally outpaces memory performance, so the memory system is often the system bottleneck. For example, with a base CPI of 1, the CPU time from the last page is: CPU time = (I + 0.2 I) x Cycle time What if we could double the CPU performance so the CPI becomes 0.5, but memory performance remained the same? CPU time = (0.5 I + 0.2 I) x Cycle time The overall CPU time improves by just 1.2/0.7 = 1.7 times! Speeding up only part of a system has diminishing returns.

Administrivia HW9 due EOB tomorrow HW10 due next Monday, before thanksgiving break No HW or Lab next week Lab in 375 again this week Quiz 7 after thanksgiving: cache & virtual memory

Basic main memory design There are some ways the main memory can be organized to reduce miss penalties and help with caching. For some concrete examples, let’s assume the following three steps are taken when a cache needs to load data from the main memory. It takes 1 cycle to send an address to the RAM. There is a 15-cycle latency for each RAM access. It takes 1 cycle to return data from the RAM. In the setup shown here, the buses from the CPU to the cache and from the cache to RAM are all one word wide. If the cache has one-word blocks, then filling a block from RAM (i.e., the miss penalty) would take 17 cycles. 1 + 15 + 1 = 17 clock cycles The cache controller has to send the desired address to the RAM, wait and receive the data. Main Memory Cache CPU

Miss penalties for larger cache blocks If the cache has four-word blocks, then loading a single block would need four individual main memory accesses, and a miss penalty of 68 cycles! 4 x (1 + 15 + 1) = 68 clock cycles Main Memory CPU Cache

A wider memory A simple way to decrease the miss penalty is to widen the memory and its interface to the cache, so we can read multiple words from RAM in one shot. If we could read four words from the memory at once, a four-word cache load would need just 17 cycles. 1 + 15 + 1 = 17 cycles The disadvantage is the cost of the wider buses—each additional bit of memory width requires another connection to the cache. Main Memory Cache CPU

An interleaved memory Another approach is to interleave the memory, or split it into “banks” that can be accessed individually. The main benefit is overlapping the latencies of accessing each word. For example, if our main memory has four banks, each one byte wide, then we could load four bytes into a cache block in just 20 cycles. 1 + 15 + (4 x 1) = 20 cycles Our buses are still one byte wide here, so four cycles are needed to transfer data to the caches. This is cheaper than implementing a four-byte bus, but not too much slower. Main Memory CPU Bank 0 Bank 1 Bank 2 Bank 3 Cache

Interleaved memory accesses Here is a diagram to show how the memory accesses can be interleaved. The magenta cycles represent sending an address to a memory bank. Each memory bank has a 15-cycle latency, and it takes another cycle (shown in blue) to return data from the memory. This is the same basic idea as pipelining! As soon as we request data from one memory bank, we can go ahead and request data from another bank as well. Each individual load takes 17 clock cycles, but four overlapped loads require just 20 cycles. Load word 1 Load word 2 Load word 3 Load word 4 Clock cycles 15 cycles

Which is better? Increasing block size can improve hit rate (due to spatial locality), but transfer time increases. Which cache configuration would be better? Assume both caches have single cycle hit times. Memory accesses take 1+15+1 cycles, and the interleaved memory bus is 8-bytes wide: i.e., an 16-byte memory access takes 18 cycles: 1 (send address) + 15 (memory access) + 2 (two 8-byte transfers) HINT: AMAT = Hit time + (Miss rate x Miss penalty) What is the miss penalty for a given block size? Cache #1 Cache #2 Block size 32-bytes 64-bytes Miss rate 5% 4%

Which is better? Increasing block size can improve hit rate (due to spatial locality), but transfer time increases. Which cache configuration would be better? Assume both caches have single cycle hit times. Memory accesses take 1+15+1 cycles, and the interleaved memory bus is 8-bytes wide: i.e., an 16-byte memory access takes 18 cycles: 1 (send address) + 15 (memory access) + 2 (two 8-byte transfers) HINT: AMAT = Hit time + (Miss rate x Miss penalty) Cache #1 Cache #2 Block size 32-bytes 64-bytes Miss rate 5% 4% Cache #1: Miss Penalty = 1 + 15 + 32B/8B = 20 cycles AMAT = 1 + (.05 * 20) = 2 Cache #2: Miss Penalty = 1 + 15 + 64B/8B = 24 cycles AMAT = 1 + (.04 * 24) = ~1.96

Writing Cache Friendly Code Two major rules: Repeated references to data are good (temporal locality) Stride-1 reference patterns are good (spatial locality) Example: cold cache, 4-byte words, 4-word cache blocks int sum_array_rows(int a[M][N]) { int i, j, sum = 0; for (i = 0; i < M; i++) for (j = 0; j < N; j++) sum += a[i][j]; return sum; } int sum_array_cols(int a[M][N]) { int i, j, sum = 0; for (j = 0; j < N; j++) for (i = 0; i < M; i++) sum += a[i][j]; return sum; } Miss rate = Miss rate = 1/4 = 25% 100% Adapted from Randy Bryant

Summary Writing to a cache poses a couple of interesting issues. Write-through and write-back policies keep the cache consistent with main memory in different ways for write hits. Write-around and allocate-on-write are two strategies to handle write misses, differing in whether updated data is loaded into the cache. Memory system performance depends upon the cache hit time, miss rate and miss penalty, as well as the actual program being executed. We can use these numbers to find the average memory access time. We can also revise our CPU time formula to include stall cycles. AMAT = Hit time + (Miss rate x Miss penalty) Memory stall cycles = Memory accesses x miss rate x miss penalty CPU time = (CPU execution cycles + Memory stall cycles) x Cycle time The organization of a memory system affects its performance. The cache size, block size, and associativity affect the miss rate. We can organize the main memory to help reduce miss penalties. For example, interleaved memory supports pipelined data accesses.

Latency Given By Manufacturer L1 Cache Comparison CPU K8 (Opteron) Pentium 4 Prescott Size code : 64KB TC : 12Kµops data : 64KB data : 16KB Associativity code : 2 way TC : 8 way data : 2 way data : 8 way Cache line size code : 64 bytes TC : n.a data : 64 bytes Write policy Write Back Write Through Latency Given By Manufacturer 3 cycles 4 cycles Compared to the Intel machine,the large size of the K8 L1 cache allows for bigger block size Pros: a big range of data or code in the same memory area Cons: low associativity tends to create conflicts during the caching phase.

Latency given by manufacturer L2 Cache Comparison CPU K8 Pentium 4 Prescott Size 512KB (NewCastle) 1024KB 1024KB (Hammer) Associativity 16 way 8 way Cache line size 64 bytes Latency given by manufacturer 11 cycles Bus width 128 bits 256 bits L1 relationship exclusive inclusive

L2 cache of the K8 shares lot of common features with the K7. K8 L2 cache L2 cache of the K8 shares lot of common features with the K7. The K8’s L2 cache uses a 16-way set associativity to partially compensates for the low associativity of the L1. Although the bus width in the K8 is double what the K7 offered, it still is smaller than the Intel model The K8 also includes an hardware prefetch logic, that allows to get data from memory to the L2 cache during the the memory bus idle time.