1.easylogging源码学习笔记(6)
2.Android HashMapç使ç¨
3.提高生产力的数组 8 个必备 VSCode 扩展
4.Seed Everything - 可复现的 PyTorch(一)
easylogging源码学习笔记(6)
`LOG` 是默认日志、CLOG自定义日志、码数LOG_IF条件日志
特殊日志
LOG_EVERY_N、数组LOG_AFTER_N、码数LOG_N_TIMES
for (int i = 1; i <= ; ++i) {
LOG_EVERY_N(2,数组 INFO) << "Logged every second iter";
}// 5 logs written; 2, 4, 6, 7,
for (int i = 1; i <= ; ++i) {
LOG_AFTER_N(2, INFO) << "Log after 2 hits; " << i;
}// 8 logs written; 3, 4, 5, 6, 7, 8, 9,
for (int i = 1; i <= ; ++i) {
LOG_N_TIMES(3, INFO) << "Log only 3 times; " << i;
}// 3 logs writter; 1, 2, 3
条件日志和特殊日志可以搭配使用
* `VLOG_IF(condition, verbose-level)`
* `CVLOG_IF(condition, verbose-level, loggerID)`
* `VLOG_EVERY_N(n, verbose-level)`
* `CVLOG_EVERY_N(n, verbose-level, loggerID)`
* `VLOG_AFTER_N(n, verbose-level)`
* `CVLOG_AFTER_N(n, verbose-level, loggerID)`
* `VLOG_N_TIMES(n, verbose-level)`
* `CVLOG_N_TIMES(n, verbose-level, loggerID)`
日志详细等级判定
if (VLOG_IS_ON(2)) {
// Verbosity level 2 is on for this file
}
性能追踪
* `TIMED_FUNC(obj-name)`
* `TIMED_SCOPE(obj-name, block-name)`
* `TIMED_BLOCK(obj-name, block-name)`
这些宏实际上都是关于el::base::type::PerformanceTrackerPtr,一个指向el::base::PerformanceTracker的码数彩虹系统源码码接指针
#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)
PerformanceTracker::PerformanceTracker(const std::string& blockName,
base::TimestampUnit timestampUnit,
const std::string& loggerId,
bool scopedLog, Level level) :
m_blockName(blockName), m_timestampUnit(timestampUnit), m_loggerId(loggerId), m_scopedLog(scopedLog),
m_level(level), m_hasChecked(false), m_lastCheckpointId(std::string()), m_enabled(false) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
// We store it locally so that if user happen to change configuration by the end of scope
// or before calling checkpoint, we still depend on state of configuration at time of construction
el::Logger* loggerPtr = ELPP->registeredLoggers()->get(loggerId, false);
m_enabled = loggerPtr != nullptr && loggerPtr->m_typedConfigurations->performanceTracking(m_level);
if (m_enabled) {
base::utils::DateTime::gettimeofday(&m_startTime);
}
#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
}
在构造函数中获取一个时间,
PerformanceTracker::~PerformanceTracker(void) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
if (m_enabled) {
base::threading::ScopedLock scopedLock(lock());
if (m_scopedLog) {
base::utils::DateTime::gettimeofday(&m_endTime);
base::type::string_t formattedTime = getFormattedTimeTaken();
PerformanceTrackingData data(PerformanceTrackingData::DataType::Complete);
data.init(this);
data.m_formattedTimeTaken = formattedTime;
PerformanceTrackingCallback* callback = nullptr;
for (const std::pair& h
: ELPP->m_performanceTrackingCallbacks) {
callback = h.second.get();
if (callback != nullptr && callback->enabled()) {
callback->handle(&data);
}
}
}
}
#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING)
}
在析构函数中获取一个时间,数组处理时间data,码数使用PerformanceTrackingCallback类型指针callback,数组并在callback->handle(&data)中处理输出。码数
由于定义了ELPP_FEATURE_PERFORMANCE_TRACKING,数组因此在初始化(INITIALIZE_EASYLOGGINGPP)中实际上是码数安装了一个base::DefaultPerformanceTrackingCallback。
在PerformanceTracker类的数组handle函数中,callback是码数一个PerformanceTrackingCallback类型指针,由于安装的数组是DefaultPerformanceTrackingCallback对象,因此是一个基类指针指向了派生类对象。处理输出的逻辑在DefaultPerformanceTrackingCallback类的handle函数中。
DefaultPerformanceTrackingCallback类的handle函数首先会将数据成员m_data的指针赋值给函数参数,并创建一个base::type::stringstream_t类型的对象ss用于构建输出内容。根据m_data的dataType,输出不同的信息。在输出时,会使用el::base::Writer类构造并输出内容。
Android HashMapç使ç¨
HashMap å HashSet æ¯ Java Collection Framework ç两个éè¦æåï¼å ¶ä¸ HashMap æ¯ Map æ¥å£ç常ç¨å®ç°ç±»ï¼HashSet æ¯ Set æ¥å£ç常ç¨å®ç°ç±»ãè½ç¶ HashMap å HashSet å®ç°çæ¥å£è§èä¸åï¼ä½å®ä»¬åºå±ç Hash åå¨æºå¶å®å ¨ä¸æ ·ï¼çè³ HashSet æ¬èº«å°±éç¨ HashMap æ¥å®ç°çã
1. ç¨åºè¯å¾å°å¤ä¸ª key-value æ¾å ¥ HashMap ä¸æ¶ï¼ä»¥å¦ä¸ä»£ç ç段为ä¾ï¼
Java代ç
HashMap<String , Double> map = new HashMap<String , Double>();
map.put("è¯æ" , .0);
map.put("æ°å¦" , .0);
map.put("è±è¯" , .2);
2.HashMap éç¨ä¸ç§æè°çâHash ç®æ³âæ¥å³å®æ¯ä¸ªå ç´ çåå¨ä½ç½®ã
å½ç¨åºæ§è¡ map.put("è¯æ" , .0); æ¶ï¼ç³»ç»å°è°ç¨"è¯æ"ç hashCode() æ¹æ³å¾å°å ¶ hashCode å¼ââæ¯ä¸ª Java 对象é½æ hashCode() æ¹æ³ï¼é½å¯éè¿è¯¥æ¹æ³è·å¾å®ç hashCode å¼ãå¾å°è¿ä¸ªå¯¹è±¡ç hashCode å¼ä¹åï¼ç³»ç»ä¼æ ¹æ®è¯¥ hashCode å¼æ¥å³å®è¯¥å ç´ çåå¨ä½ç½®ã
3. HashMap ç±»ç put(K key , V value) æ¹æ³çæºä»£ç ï¼
public V put(K key, V value)
{
// å¦æ key 为 nullï¼è°ç¨ putForNullKey æ¹æ³è¿è¡å¤ç
if (key == null)
return putForNullKey(value);
// æ ¹æ® key ç keyCode è®¡ç® Hash å¼
int hash = hash(key.hashCode());
// æç´¢æå® hash å¼å¨å¯¹åº table ä¸çç´¢å¼
int i = indexFor(hash, table.length);
// å¦æ i ç´¢å¼å¤ç Entry ä¸ä¸º nullï¼éè¿å¾ªç¯ä¸æéå e å ç´ çä¸ä¸ä¸ªå ç´
for (Entry<K,V> e = table[i]; e != null; e = e.next)
{
Object k;
// æ¾å°æå® key ä¸éè¦æ¾å ¥ç key ç¸çï¼hash å¼ç¸å
// éè¿ equals æ¯è¾æ¾å trueï¼
if (e.hash == hash && ((k = e.key) == key
|| key.equals(k)))
{
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// å¦æ i ç´¢å¼å¤ç Entry 为 nullï¼è¡¨ææ¤å¤è¿æ²¡æ Entry
modCount++;
// å° keyãvalue æ·»å å° i ç´¢å¼å¤
addEntry(hash, key, value, i);
return null;
}
提高生产力的 8 个必备 VSCode 扩展
Visual Studio Code(简称VSCode)是一个开源、轻量级且功能强大的源代码编辑器,被世界各地的开发人员广泛使用。其丰富的扩展生态系统不仅增强了用户在多种语言编码、高效调试的能力,还为编码过程引入了乐趣。本文旨在深入探讨并推荐个必备的基于Html网上书店源码VSCode扩展,它们将彻底改变您的编码体验并大幅提升生产力。无论是经验丰富的开发者还是新手,本文提供的扩展将帮助您探索市场上最优秀的VSCode扩展,以提升工作效率。
VSCode扩展是什么?
简言之,VSCode扩展是由第三方开发者提供的附加插件或组件,旨在增强Visual Studio Code编辑器的功能。这些扩展覆盖了从语言支持、调试工具到代码linter、主题设计等几乎所有开发者需求。
VSCode扩展的重要性
VSCode中的扩展在提高开发人员的工作效率方面发挥着关键作用。它们提供了管理和浏览代码的便利性,允许自动执行重复性任务、检测和修复错误、编写更清晰高效的代码,并加速整个编码过程。此外,扩展还能根据个人喜好个性化工作空间,提供舒适愉快的编码环境。
以下是8个必备的VSCode扩展,它们将有助于提高您的工作效率。
一、Console Ninja
调试通常是耗时的任务,而Console Ninja在这方面表现出色。它通过直接在VSCode编辑器中显示输出和运行时错误,显著节省了在代码编辑器和浏览器控制台之间切换的时间。Console Ninja逐行显示输出,帮助开发者详细检查代码执行流程,兼容流行JavaScript框架和库,如何用手机找到源码是满足调试需求的必备工具。
二、Indent Rainbow
Indent Rainbow旨在增强代码可读性。它通过颜色编码代码中的缩进级别,使开发者能够更好地理解和管理代码结构。使用此扩展,开发者只需跟随颜色就能轻松跟踪代码块的开始和结束位置,从而显著提高编码效率。
三、Rainglow
对于长时间编码的开发者,一个美观的界面能产生重大影响。Rainglow是VSCode的集合,包含多个优雅且赏心悦目的主题,让开发者根据个人喜好个性化编码环境。通过在不同主题之间轻松切换,开发者可以根据心情或时间调整编辑器配色方案,为编码环境增添美学元素,减少视觉疲劳,使编码更加愉快。
四、Snippet Creator
Snippet Creator是一个方便的VSCode扩展,用于创建自定义代码片段。代码段是可重用的代码块,只需敲击几下键盘即可插入代码中。使用此扩展,开发者可以快速创建自定义代码段,避免重复编码任务,提高编码效率。
五、菏泽东站离菏泽站源码VSCode Pets
VSCode并非完全严肃编码,它也有乐趣的一面!VSCode Pets扩展是一个轻松有趣的附加组件,允许开发者将各种动画宠物添加到工作区。通过选择宠物、给它们起名并与它们互动,为编码过程增添乐趣。虽然看起来有些不寻常,但这种乐趣和放松的元素经常激发创造力。
六、Toggle Quotes
Toggle Quotes是一个简单而强大的扩展,允许开发者快速在不同类型的字符串引号之间切换。在处理包含变量的字符串时,这一点特别有用。无论使用单引号、双引号还是反引号,Toggle Quotes都能轻松实现转换,避免语法错误,提高编码效率。
七、Random Everything
在进行测试时,生成随机数据可能是一项繁琐任务。输入Random Everything,一个旨在为开发者生成随机数据的扩展程序。无论需要随机数字、姓名、电子邮件还是国家/地区,此扩展都能满足需求,简化测试过程。var 12指标源码
八、Image Preview
对于Web开发者,处理图像通常是一项挑战,尤其是处理大量图像文件时。Image Preview是一个扩展程序,它通过直接在编辑器中提供图像预览来解决这一问题。使用此扩展,开发者无需离开编码环境即可查看图像文件的小预览,提高处理图像时的工作效率。
结论
VSCode的灵活性和可扩展性使其成为满足开发人员各种需求的强大工具。本文提到的扩展——Console Ninja、Rainglow、VSCode Pets、Random Everything、Indent Rainbow、Snippet Creator、Image Preview和Toggle Quotes——只是其中的一部分,它们体现了VSCode生态系统丰富性和多样性,并证明了它们如何提升编码体验和生产力。
Seed Everything - 可复现的 PyTorch(一)
为了保证实验的可复现性,许多机器学习的代码都会有一个方法叫seed everything,这个方法尝试固定随机种子以让一些随机的过程在每一次的运行中产生相同的结果。
什么是随机种子?随机数,分为真随机数和伪随机数,真随机数需要自然界中真实的随机物理现象才能产生,而对于计算机来说生成这种随机数是很难办到的。而伪随机数是通过一个初始化的值,来计算来产生一个随机序列,如果初始值是不变的,那么多次从该种子产生的随机序列也是相同的。这个初始值一般就称为种子。
Linux系统中的随机数,在Ubuntu系统中,有一个专门管理随机种子的服务systemd-random-seed.service,该服务负责在计算机启动的时候,从硬盘上加载一个随机种子文件到内核中,以作为随机初始化值在整个系统运行的过程中提供服务。Linux会通过许多硬件信息来获得这个初始化值。可以通过/dev/urandom文件来产生随机字节,然后使用od命令(该命令可将字节转换成希望的格式并打印)来获得随机数:
如果仅希望获得随机数,直接读取/dev/urandom或调用Linux系统调用getrandom()(内部也使用/dev/urandom)是不错的选择。但这种随机数是无法复现的,因为种子是由系统设置的,并且每次开机设置的种子都不一样。在“可复现”的场景中,我们需要的是一种能手动控制随机种子和读取随机序列的方式,以便可以重复获得相同随机序列的功能。
如果一个过程依赖系统产生的随机数,则称这个过程是Non Deterministic(不确定的);相反的如果一个过程对相同的输入种子都有相同的输出,则这个随机过程是Deterministic的。在“可复现”场景中,我们需要保证所有的随机过程都是Deterministic的。
/dev/random可生成“随机性”更强的随机数,但由于其依赖的系统资源更多,导致性能缓慢,因此绝大多数场景都只使用/dev/urandom。
程序中的随机数,在PyTorch中,设置随机种子的方法是torch.manual_seed(),这里就是我们所设置的随机种子,设置完毕后,如果多次调用同样的具有随机过程PyTorch方法,就会获得相同的结果,例如下面的代码在多次调用后的打印是一样的:
不论在任何机器或系统,只要使用torch==1..0版本(其他版本大概率也是OK的),输出应该都是长这样的。诶?既然随机种子产生跟系统硬件信息相关,那不同的机器至少应该不一样才对呀?上文说了,在要求“可复现”的场景下,是不能使用/dev/urandom来产生随机数的,那剩下的是需要搞清楚PyTorch是如何生成随机数的。
通过torch.manual_seed方法往下找,可以知道PyTorch生成随机数是使用了MT(梅森旋转)算法,这个算法的输入只有一个初始化值也不需要其他的环境信息。因此无论在任何机器,只要PyTorch的版本一致(算法部分没有改变)并且设置了随机种子,那么调用随机过程所产生的随机数就是一致的。C++ 在标准库中直接引入了这个方法:std::mt,而PyTorch是自己实现的,官方称性能比C++的版本要更好一些,感兴趣的话可以直接看PyTorch源码。
NumPy的np.random.seed也同样使用MT来生成随机数,因此也与硬件无关。要注意的是:np.random.seed只影响NumPy的随机过程,torch.manual_seed也只影响PyTorch的随机过程。通过下面的代码很容易验证这个结果:
由此可以得到这样的程序中所有依赖MT算法产生随机数的包,都需要手动设置随机种子,才能使整个程序的随机性是可复现的。
“根据文档,设置torch.manual_seed是对所有的设备设置随机种子。目前似乎没有单独为CPU设备设置随机种子的方法。”
CUDA的随机数,PyTorch中,还有另一个设置随机种子的方法:torch.cuda.manual_seed_all,从名字可知这是设置显卡的随机种子。
在PyTorch的内部,使用CUDA Runtime API提供的curand来设置随机种子,根据curand的文档,他们提供的所有随机数生成算法都是Deterministic的。
上面的代码看起来不够“随机”,因为在不同的GPU设备上产生了相同的结果,如果希望不同设备可以产生不同的随机数,可以这么做:
上面的代码既保证了随机性(不同设备产生不同的随机数),也保证了确定性(多次调用只产生相同结果)。在真实场景中,一般只会用相同的设备来产生随机数,因此torch.manual_seed()应该就能满足大多数需求。
不同设备之间的随机数,先问一个问题:“用GPU训练的实验结果,可以在CPU上复现吗?”。
答案是“也许可以”。
根据前文可知,CPU设置随机种子是用PyTorch官方实现的MT,而GPU是用到了CUDA Runtime API的curand。因此两套实现是完全不同的,那么对于相同的随机种子,理应产生不同的随机序列,用下面的代码可以验证:
从上面的例子中知道,对于同一个随机种子,在CPU和GPU上产出的结果是不同的,因此这种情况在GPU上的结果是无法在CPU上复现的。那为什么答案是“也许可以”呢?
因为很多代码,都会在CPU上创建Tensor,再切换到GPU上。只要不直接在GPU上创建随机变量,就可以避免这个问题。请看下面的例子:
上面的代码输出值跟CPU一致,但是device是在CUDA上。这样写可能性能不如直接在GPU上直接创建随机变量,但为了保证程序的确定性,牺牲一点性能我认为是值得的。
多进程的随机性,PyTorch的torch.utils.data.DataLoader在num_worker>0的情况下会fork出子进程,而通常又会在加载数据的时候做很多“随机变换”,那么就有必要讨论一下多进程下的随机性是怎样的,
子进程一般会保留父进程的一些状态,这也包括随机种子。因此若不做特殊处理,所有子进程都会产生和父进程相同的随机序列。请看下面的例子:
可以发现两次batch输出的结果是一样的,这是因为主进程中numpy的随机性,被两个worker保留了,因此两个worker的随机性是相同的。
“上面的结果在torch>=1.9.0是不能复现的,因为PyTorch 1.9之后DataLoader默认会给每个worker重新设置随机种子。”
这里我们需要为每一个worker设置不同的随机种子以保证随机性,但每次运行又必须要设置相同的随机种子来保证确定性,更好的代码实现如下:
这段代码将主进程的随机种子设置为,两个worker分别设置为和。因为每次运行随机种子的值是一样的,因此可以保证确定性,另外每一个worker包括主进程的随机种子都不一样,因此随机性也保证了。
类似的,对于分布式训练,也需要做类似的操作,这里考虑单机多卡的情况:
假设有两个GPU进行训练,那么第一个GPU的主进程和两个worker进程的seed为:,,;第二块GPU是:,,。
Seed Everything,最后点题,祭出一个seed_everything: