0803-PyTorch的Debug指南

pytorch完整教程目录:https://www.cnblogs.com/nickchen121/p/14662511.html

一、ipdb 介绍

很多初学 python 的同学会使用 print 或 log 调试程序,但是这只在小规模的程序下调试很方便,更好的调试应该是在一边运行的时候一边检查里面的变量和方法。

感兴趣的可以去了解 pycharm 的 debug 模式,功能也很强大,能够满足一般的需求,这里不多做赘述,我们这里介绍一个更适用于 pytorch 的一个灵活的 pdb 交互式调试工具。

Pdb 是一个交互式的调试工具,集成与 Python 标准库中,它能让你根据需求跳转到任意的 Python 代码断点、查看任意变量、单步执行代码,甚至还能修改变量的值,而没有必要去重启程序。

ipdb 则是一个增强版的 pdb,它提供了调试模式下的代码自动补全,还有更好的语法高亮和代码溯源,以及更好的内省功能,最重要的是它和 pdb 接口完全兼容,可以通过 pip install ipdb 安装。

二、ipdb 的使用

首先看一个例子,要使用 ipdb 的话,只需要在想要进行调试的地方插入 ipdb.set_trace(),当代码运行到这个地方时,就会自动进入交互式调试模式。

import ipdb

def sum(x):
r = 0
for ii in x:
r += ii
return r def mul(x):
r = 1
for ii in x:
r *= 11
return r ipdb.set_trace()
x = [1, 2, 3, 4, 5]
r = sum(x)
r = mul(x)
> /Users/mac/Desktop/jupyter/test.py(19)<module>()
18 ipdb.set_trace()
---> 19 x = [1, 2, 3, 4, 5]
20 r = sum(x) ipdb> l 1,5 # l(ist) 1,5 的缩写,查看第 1 行到第 5 行的代码
1 import ipdb
2
3
4 def sum(x):
5 r = 0 ipdb> n # n(ext) 的缩写执行下一步
> /Users/mac/Desktop/jupyter/test.py(20)<module>()
19 x = [1, 2, 3, 4, 5]
---> 20 r = sum(x)
21 r = mul(x) ipdb> s # s(tep) 的缩写,进入 sum 函数内部
--Call--
> /Users/mac/Desktop/jupyter/test.py(4)sum()
3
----> 4 def sum(x):
5 r = 0 ipdb> n # n(ext) 单步执行
> /Users/mac/Desktop/jupyter/test.py(5)sum()
4 def sum(x):
----> 5 r = 0
6 for ii in x: ipdb> n
> /Users/mac/Desktop/jupyter/test.py(6)sum()
5 r = 0
----> 6 for ii in x:
7 r += ii ipdb> u # u(p) 的缩写,调回上一层的调用
> /Users/mac/Desktop/jupyter/test.py(20)<module>()
19 x = [1, 2, 3, 4, 5]
---> 20 r = sum(x)
21 r = mul(x) ipdb> d # d(own) 的缩写,跳到调用的下一层
> /Users/mac/Desktop/jupyter/test.py(6)sum()
5 r = 0
----> 6 for ii in x:
7 r += ii ipdb> n
> /Users/mac/Desktop/jupyter/test.py(7)sum()
6 for ii in x:
----> 7 r += ii
8 return r ipdb> !r # 查看变量 r 的值,该变量名与调试命令 `r(eturn)` 冲突
0 ipdb> return # 继续运行知道函数返回
--Return--
15
> /Users/mac/Desktop/jupyter/test.py(8)sum()
7 r += ii
----> 8 return r
9 ipdb> n
> /Users/mac/Desktop/jupyter/test.py(21)<module>()
19 x = [1, 2, 3, 4, 5]
20 r = sum(x)
---> 21 r = mul(x) ipdb> x # 查看变量 x
[1, 2, 3, 4, 5] ipdb> x[0] = 10000 # 修改变量 x ipdb> x
[10000, 2, 3, 4, 5] ipdb> b 12 # b(reak) 的缩写,在第 10 行设置断点
Breakpoint 1 at /Users/mac/Desktop/jupyter/test.py:12 ipdb> c # c(ontinue) 的缩写,继续运行,直到遇到断点
> /Users/mac/Desktop/jupyter/test.py(12)mul()
11 def mul(x):
1--> 12 r = 1
13 for ii in x: ipdb> return # 可以看到计算的是修改之后的 x 的乘积
--Return--
1200000
> /Users/mac/Desktop/jupyter/test.py(15)mul()
14 r *= ii
---> 15 return r
16 ipdb> q # q(uit) 的缩写,退出 debug

上述只是给出了 ipdb 的一部分使用方法,关于 ipdb 还有一些小的使用技巧:

  • 键能够自动补齐,补齐用法和 IPython 中的类似
  • j(ump) 能够跳过中间某些行的代码的执行
  • 可以直接在 ipdb 中修改变量的值
  • help 能够查看调试命令的用法,比如 h h 可以查看 help 命令的用法,h j(ump) 能够查看 j(ump) 命令的用法

三、在 PyTorch 中 Debug

PyTorch 作为一个动态图框架,和 ipdb 结合使用能够让调试过程更加便捷,下面我们将距离说明以下三点:

  • 如何在 PyTorch 中查看神经网络各个层的输出
  • 如何在 PyTorch 中分析各个参数的梯度
  • 如何动态修改 PyTorch 的训练流程

首先,运行上一篇文章给出的“猫狗大战”程序:python main.py train --debug-file='debug/debug.txt'

程序运行一段时间后,在debug目录下创建debug.txt标识文件,当程序检测到这个文件存在时,会自动进入debug模式。

99it [00:17,  6.07it/s]loss: 0.22854854568839075
119it [00:21, 5.79it/s]loss: 0.21267264398435753
139it [00:24, 5.99it/s]loss: 0.19839374726372108
> e:/Users/mac/Desktop/jupyter/mdFile/deeplearning/main.py(80)train()
79 loss_meter.reset()
---> 80 confusion_matrix.reset()
81 for ii, (data, label) in tqdm(enumerate(train_dataloader)): ipdb> break 88 # 在第88行设置断点,当程序运行到此处进入debug模式
Breakpoint 1 at e:/Users/mac/Desktop/jupyter/mdFile/deeplearning/main.py:88 ipdb> # 打印所有参数及其梯度的标准差
for (name,p) in model.named_parameters(): \
print(name,p.data.std(),p.grad.data.std())
model.features.0.weight tensor(0.2615, device='cuda:0') tensor(0.3769, device='cuda:0')
model.features.0.bias tensor(0.4862, device='cuda:0') tensor(0.3368, device='cuda:0')
model.features.3.squeeze.weight tensor(0.2738, device='cuda:0') tensor(0.3023, device='cuda:0')
model.features.3.squeeze.bias tensor(0.5867, device='cuda:0') tensor(0.3753, device='cuda:0')
model.features.3.expand1x1.weight tensor(0.2168, device='cuda:0') tensor(0.2883, device='cuda:0')
model.features.3.expand1x1.bias tensor(0.2256, device='cuda:0') tensor(0.1147, device='cuda:0')
model.features.3.expand3x3.weight tensor(0.0935, device='cuda:0') tensor(0.1605, device='cuda:0')
model.features.3.expand3x3.bias tensor(0.1421, device='cuda:0') tensor(0.0583, device='cuda:0')
model.features.4.squeeze.weight tensor(0.1976, device='cuda:0') tensor(0.2137, device='cuda:0')
model.features.4.squeeze.bias tensor(0.4058, device='cuda:0') tensor(0.1798, device='cuda:0')
model.features.4.expand1x1.weight tensor(0.2144, device='cuda:0') tensor(0.4214, device='cuda:0')
model.features.4.expand1x1.bias tensor(0.4994, device='cuda:0') tensor(0.0958, device='cuda:0')
model.features.4.expand3x3.weight tensor(0.1063, device='cuda:0') tensor(0.2963, device='cuda:0')
model.features.4.expand3x3.bias tensor(0.0489, device='cuda:0') tensor(0.0719, device='cuda:0')
model.features.6.squeeze.weight tensor(0.1736, device='cuda:0') tensor(0.3544, device='cuda:0')
model.features.6.squeeze.bias tensor(0.2420, device='cuda:0') tensor(0.0896, device='cuda:0')
model.features.6.expand1x1.weight tensor(0.1211, device='cuda:0') tensor(0.2428, device='cuda:0')
model.features.6.expand1x1.bias tensor(0.0670, device='cuda:0') tensor(0.0162, device='cuda:0')
model.features.6.expand3x3.weight tensor(0.0593, device='cuda:0') tensor(0.1917, device='cuda:0')
model.features.6.expand3x3.bias tensor(0.0227, device='cuda:0') tensor(0.0160, device='cuda:0')
model.features.7.squeeze.weight tensor(0.1207, device='cuda:0') tensor(0.2179, device='cuda:0')
model.features.7.squeeze.bias tensor(0.1484, device='cuda:0') tensor(0.0381, device='cuda:0')
model.features.7.expand1x1.weight tensor(0.1235, device='cuda:0') tensor(0.2279, device='cuda:0')
model.features.7.expand1x1.bias tensor(0.0450, device='cuda:0') tensor(0.0100, device='cuda:0')
model.features.7.expand3x3.weight tensor(0.0609, device='cuda:0') tensor(0.1628, device='cuda:0')
model.features.7.expand3x3.bias tensor(0.0132, device='cuda:0') tensor(0.0079, device='cuda:0')
model.features.9.squeeze.weight tensor(0.1093, device='cuda:0') tensor(0.2459, device='cuda:0')
model.features.9.squeeze.bias tensor(0.0646, device='cuda:0') tensor(0.0135, device='cuda:0')
model.features.9.expand1x1.weight tensor(0.0840, device='cuda:0') tensor(0.1860, device='cuda:0')
model.features.9.expand1x1.bias tensor(0.0177, device='cuda:0') tensor(0.0033, device='cuda:0')
model.features.9.expand3x3.weight tensor(0.0476, device='cuda:0') tensor(0.1393, device='cuda:0')
model.features.9.expand3x3.bias tensor(0.0058, device='cuda:0') tensor(0.0030, device='cuda:0')
model.features.10.squeeze.weight tensor(0.0872, device='cuda:0') tensor(0.1676, device='cuda:0')
model.features.10.squeeze.bias tensor(0.0484, device='cuda:0') tensor(0.0088, device='cuda:0')
model.features.10.expand1x1.weight tensor(0.0859, device='cuda:0') tensor(0.2145, device='cuda:0')
model.features.10.expand1x1.bias tensor(0.0160, device='cuda:0') tensor(0.0025, device='cuda:0')
model.features.10.expand3x3.weight tensor(0.0456, device='cuda:0') tensor(0.1429, device='cuda:0')
model.features.10.expand3x3.bias tensor(0.0070, device='cuda:0') tensor(0.0021, device='cuda:0')
model.features.11.squeeze.weight tensor(0.0786, device='cuda:0') tensor(0.2003, device='cuda:0')
model.features.11.squeeze.bias tensor(0.0422, device='cuda:0') tensor(0.0069, device='cuda:0')
model.features.11.expand1x1.weight tensor(0.0690, device='cuda:0') tensor(0.1400, device='cuda:0')
model.features.11.expand1x1.bias tensor(0.0138, device='cuda:0') tensor(0.0022, device='cuda:0')
model.features.11.expand3x3.weight tensor(0.0366, device='cuda:0') tensor(0.1517, device='cuda:0')
model.features.11.expand3x3.bias tensor(0.0109, device='cuda:0') tensor(0.0023, device='cuda:0')
model.features.12.squeeze.weight tensor(0.0729, device='cuda:0') tensor(0.1736, device='cuda:0')
model.features.12.squeeze.bias tensor(0.0814, device='cuda:0') tensor(0.0084, device='cuda:0')
model.features.12.expand1x1.weight tensor(0.0977, device='cuda:0') tensor(0.1385, device='cuda:0')
model.features.12.expand1x1.bias tensor(0.0102, device='cuda:0') tensor(0.0032, device='cuda:0')
model.features.12.expand3x3.weight tensor(0.0365, device='cuda:0') tensor(0.1312, device='cuda:0')
model.features.12.expand3x3.bias tensor(0.0038, device='cuda:0') tensor(0.0026, device='cuda:0')
model.classifier.1.weight tensor(0.0285, device='cuda:0') tensor(0.0865, device='cuda:0')
model.classifier.1.bias tensor(0.0362, device='cuda:0') tensor(0.0192, device='cuda:0') ipdb> opt.lr # 查看学习率
0.001 ipdb> opt.lr = 0.002 # 更改学习率 ipdb> for p in optimizer.param_groups: \
p['lr'] = opt.lr ipdb> model.save() # 保存模型
'checkpoints/squeezenet_20191004212249.pth' ipdb> c # 继续运行,直到第88行暂停
222it [16:38, 35.62s/it]> e:/Users/mac/Desktop/jupyter/mdFile/deeplearning/main.py(88)train()
87 optimizer.zero_grad()
1--> 88 score = model(input)
89 loss = criterion(score, target) ipdb> s # 进入model(input)内部,即model.__call__(input)
--Call--
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\module.py(537)__call__()
536
--> 537 def __call__(self, *input, **kwargs):
538 for hook in self._forward_pre_hooks.values(): ipdb> n # 下一步
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\module.py(538)__call__()
537 def __call__(self, *input, **kwargs):
--> 538 for hook in self._forward_pre_hooks.values():
539 result = hook(self, input) ipdb> n # 下一步
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\module.py(544)__call__()
543 input = result
--> 544 if torch._C._get_tracing_state():
545 result = self._slow_forward(*input, **kwargs) ipdb> n # 下一步
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\module.py(547)__call__()
546 else:
--> 547 result = self.forward(*input, **kwargs)
548 for hook in self._forward_hooks.values(): ipdb> s # 进入forward函数内容
--Call--
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\loss.py(914)forward()
913
--> 914 def forward(self, input, target):
915 return F.cross_entropy(input, target, weight=self.weight, ipdb> input # 查看input变量值
tensor([[4.5005, 2.0725],
[3.5933, 7.8643],
[2.9086, 3.4209],
[2.7740, 4.4332],
[6.0164, 2.3033],
[5.2261, 3.2189],
[2.6529, 2.0749],
[6.3259, 2.2383],
[3.0629, 3.4832],
[2.7008, 8.2818],
[5.5684, 2.1567],
[3.0689, 6.1022],
[3.4848, 5.3831],
[1.7920, 5.7709],
[6.5032, 2.8080],
[2.3071, 5.2417],
[3.7474, 5.0263],
[4.3682, 3.6707],
[2.2196, 6.9298],
[5.2201, 2.3034],
[6.4315, 1.4970],
[3.4684, 4.0371],
[3.9620, 1.7629],
[1.7069, 7.8898],
[3.0462, 1.6505],
[2.4081, 6.4456],
[2.1932, 7.4614],
[2.3405, 2.7603],
[1.9478, 8.4156],
[2.7935, 7.8331],
[1.8898, 3.8836],
[3.3008, 1.6832]], device='cuda:0', grad_fn=<AsStridedBackward>) ipdb> input.data.mean() # 查看input的均值和标准差
tensor(3.9630, device='cuda:0')
ipdb> input.data.std()
tensor(1.9513, device='cuda:0') ipdb> u # 跳回上一层
> c:\programdata\anaconda3\lib\site-packages\torch\nn\modules\module.py(547)__call__()
546 else:
--> 547 result = self.forward(*input, **kwargs)
548 for hook in self._forward_hooks.values(): ipdb> u # 跳回上一层
> e:/Users/mac/Desktop/jupyter/mdFile/deeplearning/main.py(88)train()
87 optimizer.zero_grad()
1--> 88 score = model(input)
89 loss = criterion(score, target) ipdb> clear # 清除所有断点
Clear all breaks? y
Deleted breakpoint 1 at e:/Users/mac/Desktop/jupyter/mdFile/deeplearning/main.py:88 ipdb> c # 继续运行,记得先删除"debug/debug.txt",否则很快又会进入调试模式
59it [06:21, 5.75it/s]loss: 0.24856307208538073
76it [06:24, 5.91it/s]

当我们想要进入 debug 模式,修改程序中某些参数值或者想分析程序时,就可以通过创建 debug 标识文件,此时程序会进入调试模式,调试完成之后删除这个文件并在 ipdb 调试接口输入 c 继续运行程序。如果想退出程序,也可以使用这种方式,先创建 debug 标识文件,然后输入 quit 在退出 debug 的同时退出程序。这种退出程序的方式,与使用 Ctrl + C 的方式相比更安全,因为这能保证数据加载的多进程程序也能正确地退出,并释放内存、显存等资源。

PyTorch 和 ipdb 集合能完成很多其他框架所不能完成或很难完成的功能。根据笔者日常使用的总结,主要有以下几个部分:

  1. 通过 debug 暂停程序。当程序进入 debug 模式后,将不再执行 CPU 和 GPU 运算,但是内存和显存及相应的堆栈空间不会释放。
  2. 通过 debug 分析程序,查看每个层的输出,查看网络的参数情况。通过 u(p) 、 d(own) 、 s(tep) 等命令,能够进入指定的代码,通过 n(ext) 可以单步执行,从而看到每一层的运算结果,便于分析网络的数值分布等信息。
  3. 作为动态图框架, PyTorch 拥有 Python 动态语言解释执行的优点,我们能够在运行程序时,用过 ipdb 修改某些变量的值或属性,这些修改能够立即生效。例如可以在训练开始不久根据损失函数调整学习率,不必重启程序。
  4. 如果在 IPython 中通过 %run 魔法方法运行程序,那么在程序异常退出时,可以使用 %debug 命令,直接进入 debug 模式,通过 u(p) 和 d(own) 跳到报错的地方,查看对应的变量,找出原因后修改相应的代码即可。有时我们的模式训练了好几个小时,却在将要保存模式之前,因为一个小小的拼写错误异常退出。此时,如果修改错误再重新运行程序又要花费好几个小时,太浪费时间。因此最好的方法就是看利用 %debug 进入调试模式,在调试模式中直接运行 model . save() 保存模型。在 IPython 中, %pdb 魔术方法能够使得程序出现问题后,不用手动输入 %debug 而自动进入 debug 模式,建议使用。

四、 通过PyTorch实现项目中容易遇到的问题

PyTorch 调用 CuDNN 报错时,报错信息诸如 CUDNN_STATUS_BAD_PARAM,从这些报错内容很难得到有用的帮助信息,最后先利用 PCU 运行代码,此时一般会得到相对友好的报错信息,例如在 ipdb 中执行 model.cpu() (input.cpu()), PyTorch 底层的 TH 库会给出相对比较详细的信息。

常见的错误主要有以下几种:

  • 类型不匹配问题。例如 CrossEntropyLoss 的输入 target 应该是一个 LongTensor ,而很多人输入 FloatTensor 。
  • 部分数据忘记从 CPU 转移到 GPU 。例如,当 model 存放于 GPU 时,输入 input 也需要转移到 GPU 才能输入到 model 中。还有可能就是把多个 model 存放于一个 list 对象,而在执行 model.cuda() 时,这个 list 中的对象是不会被转移到 CUDA 上的,正确的用法是用 ModuleList 代替。
  • Tensor 形状不匹配。此类问题一般是输入数据形状不对,或是网络结构设计有问题,一般通过 u(p) 跳到指定代码,查看输入和模型参数的形状即可得知。

此外,可能还会经常遇到程序正常运行、没有报错,但是模型无法收敛的问题。例如对于二分类问题,交叉熵损失一直徘徊在 0.69 附近(ln2),或者是数值出现溢出等问题,此时可以进入 debug 模式,用单步执行查看,每一层输出的均值和方差,观察从哪一层的输出开始出现数值异常。还要查看每个参数梯度的均值和方差,查看是否出现梯度消失或者梯度爆炸等问题。一般来说,通过再激活函数之前增加 BatchNorm 层、合理的参数初始化、使用 Adam 优化器、学习率设为0.001,基本就能确保模型在一定程度收敛。

五、第八章总结

本章带同学们从头实现了一个 Kaggle 上的经典竞赛,重点讲解了如何合理地组合安排程序,同时介绍了一些在PyTorch中调试的技巧,下章将正式的进入编程实战之旅,其中一些细节不会再讲的如此详细,做好心理准备。

0803-PyTorch的Debug指南的更多相关文章

  1. pytorch Debug —交互式调试工具Pdb (ipdb是增强版的pdb)-1-在pytorch中使用

    参考深度学习框架pytorch:入门和实践一书第六章 以深度学习框架PyTorch一书的学习-第六章-实战指南为前提 在pytorch中Debug pytorch作为一个动态图框架,与ipdb结合能为 ...

  2. 万字综述,核心开发者全面解读PyTorch内部机制

    斯坦福大学博士生与 Facebook 人工智能研究所研究工程师 Edward Z. Yang 是 PyTorch 开源项目的核心开发者之一.他在 5 月 14 日的 PyTorch 纽约聚会上做了一个 ...

  3. jmeter sampler maven项目排错记

    eclipse 创建的maven项目,引入jar包之后出现红色叹号,一直找不到原因,连main方法都无法运行,提示找不到类: 错误: 找不到或无法加载主类 soapsampler.SoapSample ...

  4. 深度学习框架PyTorch一书的学习-第六章-实战指南

    参考:https://github.com/chenyuntc/pytorch-book/tree/v1.0/chapter6-实战指南 希望大家直接到上面的网址去查看代码,下面是本人的笔记 将上面地 ...

  5. (转载)PyTorch代码规范最佳实践和样式指南

    A PyTorch Tools, best practices & Styleguide 中文版:PyTorch代码规范最佳实践和样式指南 This is not an official st ...

  6. pytorch Debug —交互式调试工具Pdb (ipdb是增强版的pdb)-1-使用说明

    初学时大多使用print或log调试程序,这在小规模的程序下很方便 但是更好的方法是一边运行一边检查里面的变量和方法 1.Pdb Pdb是一个交互式的调试工具,集成于Python标准库中 Pdb能让你 ...

  7. Linux Kernel - Debug Guide (Linux内核调试指南 )

    http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级 ...

  8. pytorch 入门指南

    两类深度学习框架的优缺点 动态图(PyTorch) 计算图的进行与代码的运行时同时进行的. 静态图(Tensorflow <2.0) 自建命名体系 自建时序控制 难以介入 使用深度学习框架的优点 ...

  9. pytorch 0.4.0迁移指南

    总说 由于pytorch 0.4版本更新实在太大了, 以前版本的代码必须有一定程度的更新. 主要的更新在于 Variable和Tensor的合并., 当然还有Windows的支持, 其他一些就是支持s ...

随机推荐

  1. Kilo 使用教程

    写了这么多篇 WireGuard 相关的保姆教程,今天终于牵扯到 Kubernetes 了,不然怎么对得起"云原生"这三个字.如果看到这篇文章的你仍然是个 WireGuard 新手 ...

  2. linux 设置DNS解决,不能ping 域名的问题

    vi /etc/resolv.conf nameserver 114.114.114.114

  3. rest framework ViewSet

    ViewSets 路由选择确定要用于一个请求哪个控制器之后,控制器负责做出请求的感并产生相应的输出. - Ruby on Rails的文档 Django的REST框架允许你的逻辑一组在一个类中的相关意 ...

  4. P4847 银河英雄传说V2 题解(Splay)

    题目链接 P4847 银河英雄传说V2 解题思路 我天哪!!!\(splay\)在\(rotate\)的时候先\(upd(y)\)再\(upd(x)\)!!以后不能再因为这个\(WA\)一晚上了!!! ...

  5. python常用数据处理库

    Python之所以能够成为数据分析与挖掘领域的最佳语言,是有其独特的优势的.因为他有很多这个领域相关的库可以用,而且很好用,比如Numpy.SciPy.Matploglib.Pandas.Scikit ...

  6. Mardown语法

    1.什么是Markdown Mardown是一种文本标记语言,使用它,能让我们更加专注于内容的输出,而不是排版样式. 我们平常使用的.txt文档书写的文字是没有样式的,使用Markdown语法就可以给 ...

  7. C语言宏的使用

    使用条件宏进行条件编译 譬如,对于同一份代码,我想编译出两个不同的版本,在其中一个版本中去掉某一部分功能, 这时可以通过条件宏判断是否编译,例: 如果不使用条件宏进行控制,想编译两个不同版本的程序,就 ...

  8. OpenGL 绘制你的 github skyline 模型

    前言 好久没更新博客了,上一篇文章还是在去年6月份,乍一看居然快要过去一年了,不时还能看到粉丝数和排名在涨,可是却一直没有内容更新,怪不好意思的- -(主要是一直没想好要写些什么,中间也有过一些想法, ...

  9. Android 之 SimpleAdapter 学习笔记

    •SimpleAdapter简介 simpleAdapter 的扩展性最好,可以定义各种各样的布局出来: 可以放上ImageView(图片),还可以放上Button(按钮),CheckBox(复选框) ...

  10. 学会使用 Mysql show processlist 排查问题

    mysql show full processlist 查看当前线程处理情况 事发现场 每次执行看到的结果应该都有变化,因为是实时的,所以我定义为:"事发现场",每次执行就相当于现 ...