Introduction
本实验的目的:
- 我们将测试AList 和 SLList 运行各种方法时, 所花费的时间.
- 了解调试器的三个新的功能: 条件断点, 恢复按钮, 执行断点.
List61B的测试部分
内容: timingtest包中的代码.
计时AList
AList操作的数据是一整块空间, 我们随时可能会遇到空间不够, 创造新空间的情况. 每一次调整空间, 我们有两种方法:
- 加法调整
- 乘法调整
在本包中, 我们使用的是加法调整大小的策略.
本部分的实验, 我们的目的是编写代码, 测试不同情况的ALList实例中AddList方法的大小. (AddLast 遇到空间不足, 会建立新的数组分配空间.)
其中总共有4列:
- 第一列N: 数据结构的大小(即存储的元素的个数 == size)
- 第二列s: 完成所有的操作所需要的时间
- 第三列#ops: 调用addLast的次数
- 第四列microsec/op: 在ops次操作下, 平均每次调用花费多长时间.
tips:
- 其中N和#ops是重复的, 因为我们会从构造一个数据结构开始实验. 所以说调用了多少次addLast == 存储了多少元素.
- 每个机器关于运行时间都会有差异.
结论1: addLast不是恒定时间, 每次调用会随着列表的大小而产生很大的变化. 128000次调用平均时间374ms, 1000次调用平均时间0.2ms.
任务: 在TimeAList类中添加一个函数public void timeAListConstruction函数, 该函数会生成表格.
提供的方法:
printTimingTable(AList<Integer> Ns, AList<Double> times, AList<Integer> opCounts)
参数:
NS列表: 第一列(元素个数).
times列表: 第二列(花费时间).
opCounts列表: 第三列(操作次数).
而第四列会自动计算.
结果:
打印时间测试表, 以0位基准, i行对应列表中的第i个元素.
关于存储的时间, 可以使用Stopwatch类计算.
使用方法:
- 建立Stopwatch实例的时候, 计时开始
- 使用Stopwatch实例的elapsedTIme()方法时, 计时结束
Stopwatch sw = new Stopwatch(); // 开始计时
int fib41 = fib(41);
double timeInSeconds = sw.elapsedTime(); // 计时结束
完成该任务的过程:
- 构造AList实例
- 开始计时
- 执行次数
- 得到计时的时间
- 将元素数量, 次数, 执行的时间全部添加到NS, times, opCount列表中
编写的测试实例:
private final static int thousand = 1000;
public static void timeAListConstruction() {
// TODO: YOUR CODE HERE
AList Ns = new AList<Integer>();
AList times = new AList<Double>();
AList opCounts = new AList<Integer>();
for (int i = 1 * thousand; i <= 1000 * thousand; i += 100 * thousand) {
addItem(i, Ns, times, opCounts);
}
printTimingTable(Ns, times, opCounts);
}
/**
* 增加n个元素, Ns收集元素个数, times收集时间, opCount收集操作次数.
* */
public static void addItem(int n, AList<Integer> Ns, AList<Double> times, AList<Integer> opCounts){
AList test = new AList();
Stopwatch sw = new Stopwatch();
for (int i = 0; i < n; i += 1) {
test.addLast(i);
}
Double elapsed_time = sw.elapsedTime();
Ns.addLast(n);
opCounts.addLast(n);
times.addLast(elapsed_time);
}
对应不同的情况的表现:
- 对于resize(size * 1.5)
- 对于resize(size + 1000)
结论:
如果需要进行多次的插入操作, 使用加法调整数组大小, 花费时间巨大. 而如果使用乘法进行调整, 花费时间则会小很多.
计时SLList的getLast
有时, 方法运行时, 会对数据结构有所依赖.
方法:
private final static int THOUSAND = 1000;
public static void timeGetLast() {
// TODO: YOUR CODE HERE
AList Ns = new AList<Integer>();
AList times = new AList<Double>();
AList opCounts = new AList<Integer>();
for (int i = 10000; i <= 100 * THOUSAND; i += 10 * THOUSAND) {
getItem(i, Ns, times, opCounts);
}
printTimingTable(Ns, times,opCounts);
}
/**
* 增加n个元素 == 对nth元素进行getLast
* Ns收集元素个数. opCount收集增加的元素的个数.
* times收集花费的时间.
* */
public static void getItem(int n, AList<Integer> Ns, AList<Double> times, AList<Integer> opCounts ){
/*create an SLList*/
SLList list = new SLList<Integer>();
for(int i = 0; i < n; i++){
list.addLast(1);
}
Stopwatch sw = new Stopwatch();
for(int i = 0; i < 10 * THOUSAND; i++){
list.getLast();
}
double timeInSeconds = sw.elapsedTime();
Ns.addLast(n);
times.addLast(timeInSeconds);
opCounts.addLast(n);
}
结论:
如果使用的是链表形式, 随着链表的变大, 对于最后一个元素的存取效率会降低很多.
如果想要让getLast加快, 可以增加一个对于最后一个结点的引用. 即可以直接找到最后一个结点.
Randomized Comparison Tests
对于这一次的实验, 确定使用的是randomtest包中的代码, 而不是timingtest包中的代码.
使用的测试方法: 比较测试
我们有同一个类的两种实现. 一种正确, 一种正在开发, 未得验证. 比较两者, 发现错误.
提供的类:
- ALIstNoReasizing: 不支持任何调整大小的操作, 只拥有1000个元素, 且永远无法容纳超过1000个元素.
- BuggyAList: 可以根据数据量调整大小.
测试方法:
简单测试:
两个类各增加三个元素, 然后移出最后一个元素, 查看结果是否相同.
该方法测试强度不够, 不足以发现问题所在.
public class testThreeAddThreeRemove {
private BuggyAList test;
private AListNoResizing correct;
@Test
public void add() {
test = new BuggyAList();
correct = new AListNoResizing();
for (int i = 0; i < 3; i += 1) {
test.addLast(i);
correct.addLast(i);
}
assertEquals(correct.removeLast(), test.removeLast());
}
}
随机函数调用
对两个类进行随机调用 比较多次, 并且使用JUnit的方法验证它们是否使用返回相同的值.
begguer特点
- resume | 恢复:
作用: 继续执行, 直到下一次的断点处
- conditional breakpoints | 条件断点
作用: 给断点增加条件, 增加到满足该条件的断点处.
右击断点, 填写condition
StdRandom.uniform(start, end): 返回[start, end)内的随机整数.
- Execution Breakpoints | 执行断点
作用: 当遇到异常时, 我们停止代码, 并可视化代码崩溃的情况.
工作流程:
- 点击 Run -> View Breakpoints
- 勾选 any excepttion -> 然后在Condition中输入 具体的异常(可以从JUnit给出的异常问题中复制粘贴)
任务1:
- 随机的数值从0和1 变成 0, 1, 2, 3
- 添加getLast方法 和 removeLast方法 的 测试.
考虑因素:
因为数据结构的限制, getLast 和 removeLast方法执行时, size必须大于0, 否则会报错.
int operationNumber = StdRandom.uniform(0, 4);
// addLast
if (operationNumber == 0) {
int randVal = StdRandom.uniform(0, 100);
L.addLast(randVal);
System.out.println("addLast(" + randVal + ")");
// 1: size
} else if (operationNumber == 1) {
int size = L.size();
System.out.println("size: " + size);
// 2: getLast
} else if (L.size() > 0 && operationNumber == 2) {
int res = L.getLast();
System.out.println("getLast: " + res);
// 3: removeLast
} else if (L.size() > 0 && operationNumber == 3) {
int res = L.removeLast();
System.out.println("removeLast: " + res);
}
}
任务2:
- 在测试中增加BuggyList的方法, 并对其和AListNoRresizing的方法所得结果进行比较, 找出最后的问题到底是什么
- 可以使用执行断点的方法找出是什么导致的异常.
@Test
// i can't think of position where error happens.
public void randomizedTest() {
AListNoResizing<Integer> L = new AListNoResizing<>();
BuggyAList<Integer> B = new BuggyAList<>();
int N = 5000;
for (int i = 0; i < N; i += 1) {
int operationNumber = StdRandom.uniform(0, 4);
// addLast
if (operationNumber == 0) {
int randVal = StdRandom.uniform(0, 100);
L.addLast(randVal);
B.addLast(randVal);
System.out.println("addLast(" + randVal + ")");
// 1: size
} else if (operationNumber == 1) {
int size = L.size();
int size1 = B.size();
System.out.println("size: " + size);
assertEquals(size, size1);
// 2: getLast
} else if (L.size() > 0 && operationNumber == 2) {
int res = L.getLast();
int res1 = B.getLast();
System.out.println("getLast: " + res);
assertEquals(res, res1);
// 3: removeLast
} else if (L.size() > 0 && operationNumber == 3) {
int res = L.removeLast();
int res1 = B.removeLast();
System.out.println("removeLast: " + res);
assertEquals(res, res1);
}
}
}
通过执行断点发现的异常:
remove导致的resize方法出现了错误.
新建的数组不足以将原有的数组的元素复制到新的数组中.
经过排查发现:
本来想要size < 底层数组的长度1/4时, 提高空间利用率, 减少分配的数组容量. 将数组改变成原来的数组的1/4
但是在编写代码的过程中, 新数组的长度, 写成了size / 4. 即需要使用的数组空间的1/4.
修改后的代码:
public Item removeLast() {
if ((size < items.length / 4) && (size > 4)) {
// resize(size / 4);
resize(items.length / 4);
}
Item x = getLast();
items[size - 1] = null;
size = size - 1;
return x;
}