Signals and slots

因为简历里面写了Qt,所以..特别的复习了一下Signals and slots的实现。

首先,Signals and slots是观察者模式的一种实现。整个机制包括四个关键字,Signals,Slots,Connect和Emit。以下是一个官方的Demo。

class Counter : public QObject
{
    Q_OBJECT
    int m_value;
public:
    int value() const { return m_value; }
public slots:
    void setValue(int value);
signals:
    void valueChanged(int newValue);
};

void Counter::setValue(int value)
{
    if (value != m_value) {
        m_value = value;
        emit valueChanged(value);
    }
}
int main()
{
  Counter a, b;
  QObject::connect(&a, SIGNAL(valueChanged(int)),
                   &b, SLOT(setValue(int)));

  a.setValue(12);  // a.value() == 12, b.value() == 12
}

Signals && Slots

SIGNAL与SLOT宏会利用预编译器将一些参数转化成字符串,并且在前面添加上编码。
# define SLOT(a) "1"#a
# define SIGNAL(a) "2"#a
 。其实在类内定义的slots: signals: 都没有任何的意义。

MOC( Meta-Object Compiler)

Qt的精髓之一,所谓的元对象编译器,用于解析Qt的宏和生成代码。包括实现反射和Signals和Slots。

因此,当执行MOC时,会分析源程序中的,生成Introspection Tables,以下就是Introspection Tables的表现,重点看methods,2代表有两个methods,14代表开始的index是14。

static const uint qt_meta_data_Counter[] = {

 // content:
       7,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   14, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       1,       // signalCount

 // signals: name, argc, parameters, tag, flags
       1,    1,   24,    2, 0x06 /* Public */,

 // slots: name, argc, parameters, tag, flags
       4,    1,   27,    2, 0x0a /* Public */,

 // signals: parameters
    QMetaType::Void, QMetaType::Int,    3,

 // slots: parameters
    QMetaType::Void, QMetaType::Int,    5,

       0        // eod
};

同时还会生成String Table用于保存函数的名字。

struct qt_meta_stringdata_Counter_t {
    QByteArrayData data[6];
    char stringdata0[46];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_Counter_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = {
    {
QT_MOC_LITERAL(0, 0, 7), // "Counter"
QT_MOC_LITERAL(1, 8, 12), // "valueChanged"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 8), // "newValue"
QT_MOC_LITERAL(4, 31, 8), // "setValue"
QT_MOC_LITERAL(5, 40, 5) // "value"

    },
    "Counter
struct qt_meta_stringdata_Counter_t {
    QByteArrayData data[6];
    char stringdata0[46];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_Counter_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = {
    {
QT_MOC_LITERAL(0, 0, 7), // "Counter"
QT_MOC_LITERAL(1, 8, 12), // "valueChanged"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 8), // "newValue"
QT_MOC_LITERAL(4, 31, 8), // "setValue"
QT_MOC_LITERAL(5, 40, 5) // "value"

    },
    "Counter\0valueChanged\0\0newValue\0setValue\0"
    "value"
};
#undef QT_MOC_LITERAL
valueChanged
struct qt_meta_stringdata_Counter_t {
    QByteArrayData data[6];
    char stringdata0[46];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_Counter_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = {
    {
QT_MOC_LITERAL(0, 0, 7), // "Counter"
QT_MOC_LITERAL(1, 8, 12), // "valueChanged"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 8), // "newValue"
QT_MOC_LITERAL(4, 31, 8), // "setValue"
QT_MOC_LITERAL(5, 40, 5) // "value"

    },
    "Counter\0valueChanged\0\0newValue\0setValue\0"
    "value"
};
#undef QT_MOC_LITERAL
struct qt_meta_stringdata_Counter_t {
    QByteArrayData data[6];
    char stringdata0[46];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_Counter_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = {
    {
QT_MOC_LITERAL(0, 0, 7), // "Counter"
QT_MOC_LITERAL(1, 8, 12), // "valueChanged"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 8), // "newValue"
QT_MOC_LITERAL(4, 31, 8), // "setValue"
QT_MOC_LITERAL(5, 40, 5) // "value"

    },
    "Counter\0valueChanged\0\0newValue\0setValue\0"
    "value"
};
#undef QT_MOC_LITERAL
newValue
struct qt_meta_stringdata_Counter_t {
    QByteArrayData data[6];
    char stringdata0[46];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_Counter_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = {
    {
QT_MOC_LITERAL(0, 0, 7), // "Counter"
QT_MOC_LITERAL(1, 8, 12), // "valueChanged"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 8), // "newValue"
QT_MOC_LITERAL(4, 31, 8), // "setValue"
QT_MOC_LITERAL(5, 40, 5) // "value"

    },
    "Counter\0valueChanged\0\0newValue\0setValue\0"
    "value"
};
#undef QT_MOC_LITERAL
setValue
struct qt_meta_stringdata_Counter_t {
    QByteArrayData data[6];
    char stringdata0[46];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_Counter_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = {
    {
QT_MOC_LITERAL(0, 0, 7), // "Counter"
QT_MOC_LITERAL(1, 8, 12), // "valueChanged"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 8), // "newValue"
QT_MOC_LITERAL(4, 31, 8), // "setValue"
QT_MOC_LITERAL(5, 40, 5) // "value"

    },
    "Counter\0valueChanged\0\0newValue\0setValue\0"
    "value"
};
#undef QT_MOC_LITERAL
" "value" }; #undef QT_MOC_LITERAL

之后就是真实的调用机制的。

Signals

实现的方式很简单,就是将参数存到一个数组中然后使用QMetaObject::activate即可。其中第三个参数0,表示的是Signal的Index。

void Counter::valueChanged(int _t1)
{
    void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
Slots

这里的switch case 是MOC自动生成的,更具每个人的Id。

void Counter::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        Q_ASSERT(staticMetaObject.cast(_o));
        Counter *_t = static_cast<Counter *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 1: _t->setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        }
}
Connection

首先Qt会根据String Table 找到Signals的Index和Slots的Index。首先有几个需求,为了能够快速的找到Connection根据给定的Signal Index,同时一个Signal会有好几个Slots同时连着,因为我们需要给每一个Signal一个List,其中需要保存Objects和Slots的Index。同时,在Slots被清除,也需要能够自动在Connection中删掉相关的信息。

具体的实现就是对于每个Signal都有一个双向链表的ConnectionVector。因为链表能够快速的插入和删除。

同时Slots也有一个单向链表,其中prev并不是一个双向链表,而是在销毁时方便时而已,源码

Emit

Emit在MOC中被转换成了QMetaObject::activate

总的来说,即使在程序编译阶段,由Qt的MOC将Signals和Slots使用Metadata连接起来了,emit时调用了activate 程序,之后就调用了qt_static_metacall,之后就根据swtich case语句调用Slots即可。

Qt5的实现

以上的实现均为Qt4的基本实现,而Qt5中使用模板元对Connect有了新的实现方式。能够在编译时检查Signals和Slots的类型,同时还支持Lambda。

线程安全的问题

信号槽的执行方式有几种,参考connect的最后一个参数,这个参数经常被省略掉。其中一个是DirectConnection,就是直接连接,跟踪代码会发现,emit之后仅仅经过了signal/slot匹配就直接调用了slot函数,这种不是线程安全的,所以只能连接处于同一线程中的Qt对象,因为界面控件一般都是在UI线程创建的,所以平常我们在控件之间传输数据就不用考虑线程安全了。但是如果两个对象处于不同线程,Direct就有线程安全问题了,Qt也考虑到了这个问题,此时用Direct会被忽略,而需要用另一种连接方式,QueuedConnection,顾名思义,信号emit之后Qt会先把emit放到目标线程的事件队列中,这时emit会立即返回,等目标线程的主循环执行到了处理事件的时候才会执行slot,而Qt的这个事件队列经过了处理是线程安全的。

 

Reference

https://woboq.com/blog/how-qt-signals-slots-work.html
https://woboq.com/blog/how-qt-signals-slots-work-part2-qt5.html
https://woboq.com/blog/qmetatype-knows-your-types.html
https://woboq.com/blog/how-qt-signals-slots-work-part3-queuedconnection.html

OpenMP的原理?

故事是这样的..前几天,投了腾讯的游戏客户端开发..面试官看了我的简历,问了我OpenMP的原理?然后一面就挂了(当然还有其他问题,总之不够钻研,没有达到预期!)于是乎在图书馆找了本有关OpenMP的书看看。

OpenMP是一个顶层的并行框架,其内部兼容各种各样的线程库。具体的其将并行域交给ORT来负责,把并且隔离了具体使用的线程库。当然从编译原理的角度需要先将OpenMP的语法制导转化成C语言,再由GCC编译。ORT和EELIB的框架如下图所示:

线程管理和控制

通过线程池和EECB共同维护一个高效的并行域管理。

线程池

为了避免每次进入和退出并行域所使用的fork-join操作,OMPi实际维护了一个线程池,需要时将线程从池中取出。具体是下图所示,这里采用othr_pool_t的类型来记录,是一条单向链表,同时* H是头执政,plen标识总用共有plen个线程,plock作为访问线程池的互斥锁。注意这里的arg并非函数的arg,而是当前线程运行的EECB((Execution Entity Control Block)控制块参数。取用是,从线程池的头部一个一个取下。

EECB

EECB很大程度上实现了线程的层次关系,包括线程组和线程嵌套关系。 其中重要的几个参数有,level表示的是线程的层次结构,sibling表示同层次的线程到底有几个,parent即指向父节点的线程(有可能是自己)。

任务分担–For

`#pragma omp parallel for`非常常用,有static,dynamic,guide,默认和runtime五种调度方式。默认模式是一种特殊的静态调度,没有指定chunksize。当然书中还有section single等,但是for是最常用的。
static的例子图下图所示:

还有很多具体的操作实现包括编译原理,AST结构的变化,共享变量,私有变量,规约的实现等等,ORT具体底层实现的EECB等等,具体可以参考OpenMP编译原理及实现技术。这可能是国内第一本有关OpenMP实现的书了(而中文互联网上也几乎没有关于OpenMP底层原理的内容..

多指触控中的检测问题

首先介绍一下具体的触屏识别情况,在多指触控时,手指不可能同时落到屏幕上,比如三个手指落到屏幕上,屏幕上的touchPoints的个数就是0 –> 1 -> 2 –> 3。同样的,如果三个手指从屏幕上拿掉,个数便会是3 –> 2 –> 1 –> 0的过程。这一点随便写一个Demo应该就能够很好的验证。

接下来引入一个场景,就是如果说当前的这个程序设计在单指的情况下有一个操作(比如选中),在双指的情况下会有另外的操作(比如拖动或是缩放 Pinch)。那么在做双指动作的时候自然会先做单指动作,那如何防止做出类似选中操作呢。类似的还有,单击和双击的区分?

解决的方法也是很简单的,就是通过两次接触的时间差,因为这种时间间隔非常短,可能是0.1s左右。具体的方法是,通过定时器(比如Qt中的QTimer),在TouchEvent变化的时候,判断统计每次的TouchPoints的个数,如果同之前的个数不同,那就启动或重启计时器;如果同之前的个数相同且计时器已经Timeout,那么就可以做出具体的手势操作。

当然,这样操作会有一个小bug,就是当单指的操作时间非常短的时候会有问题。具体的情况比如,单指迅速点击的选中,可能计时器还没结束就停止触摸操作了,这一点特殊处理一下就好了。

[C++] 右值引用

根据我个人的理解,引用应当是是C++为了实现与C中的指针相同功能的一种封装。右值引用,就是为了完整实现原有C中的“指针功能”。
传统的引用往往就是左值引用,比如:

int gen = 1;
int& ref_gen = gen;
const int& conref_gen = gen;

左值引用(int& ),一般用于需要在函数内成员对象,比如std::swap。
常量左值引用(const int &),一般用于传入一个较大的数据的时候,比如一个巨大的结构体,比如在传入的时候的拷贝工作。

但是,右值引用解决的一种具体的情形是这样的,如果一个临时的变量(传完即销毁)传入其中,那么就不需要再拷贝一次内存进行赋值,只需要把指针指向临时变量的地址即可。这种,用指针非常容易实现,但是如果使用引用的方式就非常难,因此右值引用就可以避免一次拷贝的过程,最直接的影响就是可以加快程序的运行速度。

class Info
{
private:
    char* _data;
public:
    Info(const char* str = 0)
    {
        _data = new char[strlen(str) + 1];
        strcpy(_data, str);
    }
    Info(const Info& cur)
    {
        _data = new char[strlen(cur._data) + 1];
        strcpy(_data, cur._data);
    }
    Info(Info&& cur) noexcept
    {
        _data = cur._data;
        cur._data = nullptr;
    }
};
int main()
{
    Timer CurTimer;
    CurTimer.StartTimer();
    for (int i=0;i<100000;i++)
    {
        Info cur("10000000086");
        Info l(cur);
    }
    CurTimer.StopTimer();
    std::cout << "left: " << CurTimer.GetTimerMilliSec() << std::endl;
    CurTimer.StartTimer();
    for (int i = 0; i < 100000; i++)
    {
        Info cur("10000000086");
        Info r(std::move(cur));
    }
    CurTimer.StopTimer();
    std::cout << "right: " << CurTimer.GetTimerMilliSec() << std::endl;
    return 0;
}

通用引用
当右值引用和模板类型和auto搭配的时候,T&&不一定代表右值引用,也可能是一个左值引用。具体的类型跟初始化的内容有关,初始化是左值引用,那么之后的类型就是左值引用。(以下的程序说明,左值引用和原值的地址,而右值引用的内容是暂时存储到了一个新的地址里面。

template<typename T>
void func(T&& foo) 
{
    std::cout << &foo << std::endl;
}

int main()
{
    int value = 10;
    int& ref = value;
    int&& lef = value * 1;
    func(ref);
    func(lef);
    std::cout << &value << std::endl;
}
/*
output:
000000E3425EF664
000000E3425EF6C4
000000E3425EF664
*/

完美转发
如果将一个参数交给另外一个函数处理,那么那么另一个函数会不知道这个是左值还是右值。int && a并不是一个右值,而是一个绑定了右值对象的左值。因此,需要有一个函数使得传递的函数常数的类型。(以下的程序可以很好的说明参数在传递过程的类型变化。

#include <iostream>

template<typename T>
void typeprint(T& _) 
{
    std::cout << "left value" << std::endl;
}

template<typename T>
void typeprint(T&& _) 
{
    std::cout << "right value" << std::endl;
}

template<typename T>
void testForward(T&& v) 
{
    typeprint(v);
    typeprint(std::forward<T>(v));
    typeprint(std::move(v));
}

int main()
{
    testForward(1);
    std::cout << std::endl;
    int x = 1;
    testForward(x);
}

/*
output:
left value
right value
right value

left value
left value
right value
*/

STL中也使用右值引用作为参数来加速,比如emplace_back,根据cppreference可以看到,当然构造函数也是需要经过处理的,就跟给出的例子一样。

std::move 是将原本的左值临时显式的变为右值,之后原本的左值应当应当被销毁。
std::forward 用于完美转发的函数。

关于软件工程的一些想法

 

这学期选了软件工程,也就是更快速而有效的进行协作。

人工智能课程做了一个军旗AI,说是AI,不过是用Alpha-Beta剪枝,然后做暴力搜索,再加上一些优化,其实到最后比的就是谁的算法优化的更好博弈树搜索的越深,以及更好的布局(军旗的布局影响挺大的)。当时我就想,如何我写好了一个框架,剩下的人在里面优化一些函数就好了,最后的结果其实并不好。一个同学STL,git都不太会,可能就无法上手了,另一个同学在原本的框架下,又重写了半个框架(这个问题我也不知道如何解决,不知道是不是我的注释写的不够清楚),因此大部分都被我删掉了。最后其实是相当于和室友两个人完成的,毕竟调参优化什么还是挺费时间的。不过感觉这种算法类的项目,的确挺难分工的,但最后拿了rank1还是挺能够开心的。

在做软件工程的项目也会遇到这个问题,原来想是PHP的,毕竟简单好用,结果发前后端比较难分工,之后开始用flask开发做前后端协作,最后好在做出了个还算不错的成品,我主要做的就是OJ的判题内核

总的来说,敏捷开发感觉太业务了,每一次的Sprint都是为了业务,为了新的功能点,如果说是想进行大规模的重构基本是不可能的。稍微有点深度的项目感觉很难开发,敏捷开发感觉还是适合外包开发业务的简单情形,比如做个选课网平台(学校请的外包公司似乎写的bug挺多),我觉得倒是适合的。

软件工程里的测试驱动开发倒是挺有道理的,毕竟我在写OJ判题内核的时候用的就是TTD,当时写的时候其实并没有想到这个概念,之后发现还是蛮有有效的的。TTD最简单的例子就是渣哥高程的某些作业,再者比如CS231n里的作业也是。最主要的特点就是目标清晰,只要考虑接口内部实现即可。其实这就挺适合分工的,先把接口和测试写好,每个人写各自的接口就好了。

不过,最重要的还是,组内的人水平差不多,这样无论是交流还是做Code Review都能方便很多。

 

 

 

SSE/AVX SIMD初步尝试.

SSE/AVX 是其实就是SIMD指令集的主要应用(MMX太过远古现在基本不用了,AVX512现在桌面级还不支持),使用时只需要#include <immintrin.h>即可,至于具体的函数可以查阅Intel的官网 ,官网的对每个指令都非常详细的demo.

想到这个是因为,做计组作业的时候被问到一个问题,感觉挺怪的,我只能拿奔腾4和龙芯做对比了..

回到正题,初步用SIMD做了一下memset的操作,确实有比较大的提速,AVX和SSE的差距貌似并不是特别大,可能主要的时间浪费在IO上了,不过gcc里面的memset确实非常快,似乎是CPU内部对这个做了专门的提速,外加上我没有考虑上字节对齐的因素。

一句题外话,for(Loop)循环会比用指针(Pointer)做迭代慢,因为在汇编上,多了一个赋值给i的操作,但是影响貌似并不大。

最后代码里用了WIN32的API做的Timer来实现更精确的计数.

#include <iostream>
#include <windows.h>
#include <immintrin.h>
#include <ctime>

//======================Precise Timer================================
class Timer
{
private:
  unsigned long long _start_count;
  unsigned long long _stop_count;
  double _millisec_percount;
  bool _timer_state;
public:
  Timer();
  void StartTimer();
  void StopTimer();
  double GetTimerMilliSec() const;
  double GetTimerSec() const;
};

Timer::Timer()
{
  _start_count = _stop_count = 0;
  _timer_state = false;
  unsigned long long frequency;
  QueryPerformanceFrequency((LARGE_INTEGER*)&frequency);
  _millisec_percount = (double)1.0 / ((double)frequency / 1000.0);
}

void Timer::StartTimer()
{
  _timer_state = true;
  QueryPerformanceCounter((LARGE_INTEGER*)&_start_count);
}

void Timer::StopTimer()
{
  QueryPerformanceCounter((LARGE_INTEGER*)&_stop_count);
  _timer_state = false;
}

double Timer::GetTimerSec() const
{
  return GetTimerMilliSec() / (double)1000.0;
}

double Timer::GetTimerMilliSec() const
{
  return (float)(_stop_count - _start_count) * _millisec_percount;
}
//===================================================================


void Memset_Loop(int* a, int size)
{
  for (int i = 0; i < size; i++)
    a[i] = 0;
}
void Memset_Pointer(int* a, int size)
{
  for (int* i = &a[0], *end = &a[size]; i < end; i++)
    *i = 0;
}


void Memset_SSE(int* a, int size)
{
  __m128i zero = _mm_setzero_si128();
  for (__m128i* i = reinterpret_cast<__m128i*>(a), *end = reinterpret_cast<__m128i*>(&a[size]); i < end; i++)
    _mm_store_si128(i, zero);
}

void Memset_AVX(int* a, int size)
{
  __m256i zero = _mm256_setzero_si256();
  for (__m256i* i = reinterpret_cast<__m256i*>(a), *end = reinterpret_cast<__m256i*>(&a[size]); i < end; i++)
    _mm256_store_si256(i, zero);
}

void Memset_SYS(int* a, int size)
{
  memset(a, 0, sizeof(a));
}
/*
unsupported AVX512

void Memset_AVX512(int* a, int size)
{
  __m512i zero = _mm512_setzero_si512();
  for (__m512i* i = reinterpret_cast<__m512i*>(a), *end = reinterpret_cast<__m512i*>(&a[size]); i < end; i++)
    _mm512_store_si512(i, zero);
}
*/
const int MYSIZE = 100000000;

int main()
{
  int* a = new int[MYSIZE];
  for (int i = 0; i < MYSIZE; i++)
    a[i] = rand();


  std::cout << "Start!!!" << std::endl;
  Timer CurTimer;
  CurTimer.StartTimer();
  Memset_Pointer(a, MYSIZE);
  CurTimer.StopTimer();
  std::cout << "Loop   : " << CurTimer.GetTimerMilliSec() << std::endl;

  CurTimer.StartTimer();
  Memset_Pointer(a, MYSIZE);
  CurTimer.StopTimer();
  std::cout << "Pointer: " << CurTimer.GetTimerMilliSec() << std::endl;

  CurTimer.StartTimer();
  Memset_SSE(a, MYSIZE);
  CurTimer.StopTimer();
  std::cout << "SSE    : " << CurTimer.GetTimerMilliSec() << std::endl;

  CurTimer.StartTimer();
  Memset_AVX(a, MYSIZE);
  CurTimer.StopTimer();
  std::cout << "AVX    : " << CurTimer.GetTimerMilliSec() << std::endl;

  CurTimer.StartTimer();
  Memset_SYS(a, MYSIZE);
  CurTimer.StopTimer();
  std::cout << "SYS    : " << CurTimer.GetTimerMilliSec() << std::endl;
  return 0;
}

 

[C] 关于goto的想法

goto似乎一直都不推荐使用,但是我在处理动态内存申请时候,感觉不用goto挺难维护代码。比如在一个函数有多个出口的时候,函数内有动态内存申请,那么退出的时候必然要去free,但如果每个return之前都要写free,这样在修改的时候很复杂。

void func()
{
  malloc();
  for	(..)
  {
    if (..)
    {
      free(...);
      return;
    }
    if (..)
    {
      free(...);
      return;
    }
  }
  free(...)
  return;
}

如果使用Goto的话,所有的动态内存释放都放在一个地方管理,而且代码似乎更加简洁了。不过C++就不需要考虑这个问题了。

void func()
{
  malloc();
  for	(..)
  {
    if (..)
    {
      goto CLEAR;
    }
    if (..)
    {
      goto CLEAR;
    }
  }
  
CLEAR:
  free(...);
  return;
}

网上还有一些关于Goto的应用比如:
Ragel State Machine Compiler 有限状态机编译器,可以直接转混成C和C++源码

C里面还有setjmp和longjmp来完成goto的操作。

#include <stdio.h>
#include <setjmp.h>
int main()
{
  jmp_buf env;
  int ret = 0;
  ret = setjmp(env);
  printf("ret=%d\n", ret);
  if (ret == 0)
    longjmp(env, 1);
  if (ret == 1)
    longjmp(env, 2);
  return 0;
}
//	output:
//	ret = 0
//	ret = 1
//	ret = 2

[C++] 内嵌汇编和调用汇编函数

内嵌汇编

在VS中可以加入__asm来内在VS中的测试。

#include <iostream>
#include <ctime>
using namespace std;

int main()
{
  clock_t start, end;
  int Sum=0,Count=100000000;
  int Block = 20000000;
  start = clock();

  __asm 
  {
    MOV ECX, Count
  L1: PUSH ECX
    MOV EAX, 1
    MOV ECX, 20
  L2: SHL EAX, 1
    INC Sum
    LOOP L2
    POP  ECX
    LOOP L1
  }
  
  cout << Sum << endl;
  end = clock();
  cout << ((double)end - (double)start) / CLOCKS_PER_SEC << endl;
  
  start = clock();
  Sum = 0;
  for (int i = 0; i < Count; i++)
  {
    int Ax = 1;
    for (int j = 0; j < 20; j++)
    {
      Ax = Ax << 1;
      Sum++;
    }
  }
  cout << Sum << endl;
  end = clock();
  cout << ((double)end - (double)start) / CLOCKS_PER_SEC << endl;
  return 0;
}

但貌似还是C++源码快(逃。可能是现代编译器太强了。

C++调用汇编函数

TITLE AsmFindArray Procedure
.586
.model flat,C

AsmFindArray PROTO,
  Val:DWORD,Array:PTR DWORD,N:DWORD
.code
AsmFindArray PROC USES edi,
  Val:DWORD,Array:PTR DWORD,N:DWORD
  true=1
  false=0

  mov		eax,Val
  mov		ecx,N
  mov		edi,Array

  repne	scasd
  jz		rT
rF:
  mov		al,false
  jmp		short exit
rT:
  mov		al,true
exit:
  ret
AsmFindArray	ENDP
END
#include <iostream>
#include <ctime>
using namespace std;

extern "C" bool AsmFindArray(int Val, int Array[], int N);
bool FindArray(int Val, int Array[], int N)
{
  for (int i = 0; i < N; i++)
    if (Array[i] == Val)
      return true;
  return false;
}
int Num = 100000;
int A[100010];
int main()
{
  for (int i = 0; i < Num; i++)
    A[i] = i;
  int Sum = 0;

  clock_t start, end;

  start = clock();
  for (int i = 0; i <= Num * 2; i++)
    Sum += AsmFindArray(i, A, Num);
  end = clock();
  cout << Sum << endl;
  cout << "ASM:" << ((double)end - (double)start) / CLOCKS_PER_SEC << endl;

  Sum = 0;
  start = clock();
  for (int i = 0; i <= Num * 2; i++)
    Sum += FindArray(i, A, Num);
  end = clock();
  cout << Sum << endl;
  cout << "C++:" << ((double)end - (double)start) / CLOCKS_PER_SEC << endl;
  return 0;
}

VS中汇编的调用需要一些设定,用的时候可以百度一下,结果的优化还是挺明显的。

不过还是比不过开了O2优化的代码。
所以感觉一般汇编优化还是在一些不能开O优化的场合用比较好。

参考文献:Intel汇编程序设计

[C] C Traps and Pitfalls 读书记录

使用errno.h检测错误

当库文件调失败的时候,比如fopen没有相应文件,malloc没有内存等等时,就会设置errno。引用errno.h(cerrno)的时候就会有一个全局变量errno。 还可以perror,strerror使用。我们可以先判断错误之后,在检查errno找出原因。

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
int main(void)
{
    /*调用errno之前必须先将其清零*/
    errno=0;
    FILE *fp = fopen("test.txt","r");
    if(fp==NULL)
    {
  perror("Error");
        printf("errno值: %d\n",errno);
        printf("错误信息: %s\n",strerror(errno));
    }
}

使用Signal检查

信号是某种意义上的异步,可以在程序执行期间的任何一个时刻发生。有SIGABRT,SIGINT等等,可以去查询,也可以自定义信号。下面程序可以检测在程序被Ctrl+C中断。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <csignal>
#include <unistd.h>

void sighandler(int signum)
{
   printf("Caught signal %d, coming out...\n", signum);
   exit(1);
}

int main()
{
   signal(SIGINT, sighandler);
   while(1) 
   {
      printf("Going to sleep for a second...\n");
      sleep(1);
   }
   return 0;
}

 

getchar()实际上是#define getchar() getc(stdin)的宏定义。当然很多编译器也写了getchar()的函数。当然putchar也有宏定义。

宏定义在类型定义时出现问题

#define ElemType struct foo
ElemType a,b;

但是当为指针时,会变成一个指针和一个变量。

#define ElemType struct foo *
ElemType a,b;
struct foo *a,b;

词法分析中的贪心法

在C中有/,*,=等单字符符号,也有/*,==等多字符符号,词法会选择当前情况下能够组合的最长的组合情况,因此a—b与表达式 a– -b的含义相同。

 

[C++]String

COW ——共享内存,通过计数器来统计相同的字符串的个数。

SSO ——28个字节中如果短的字符串自身存储,长的字符串再内存申请。
VS下sizeof(string)=28
如果长度不超过16个字符则存在*(p+1)~*(p+4)之间,*(p+5)表示的是string的长度,如果长度超过16则会在*(p+1)存储指向该字符串的指针。

一行写法?(stringstream() << str1 << str2).str()