7 years, 12 months ago.

making program smaller

Hi

first i have not done any optimisation yet as mentioned on this question https://developer.mbed.org/questions/62171/bin-file-size/.

I am not used programming in c/c++. I am learning it through the project i am doing on mbed. I am programming on NucleF103RB with 20kB RAM and 128 kB Flash.

The problem i have is that i have reached the limit of flash memory and i am sure that through changes in code and maybe compiler optimisation i can spare a lot but i do not know what changes to do and i do not understand the reasons that are causing that much usage of memory.

In my project i am doing extensive usage of std::string (almost all variables are of that type), std::vector<std::string> and std::map<char, "own Objects">. The idea of the project is just sending some commands to the device which in turn generates some documents and returns them through the serial interface.

So my questions are: 1 - Are there any general suggestions in programming (not dependent on my project) which help using less memory. 2 - Are there compiler optimization options i can use. I am programming on eclipse and using platformio and i am using

build_flags = -g -Wl,-u,_printf_float,-u,_scanf_float

Without the "-Wl,-u,_printf_float,-u,_scanf_float" part the float arithmetic does not work thats why i need it. When i generate my project on eclipse i have like 127 kB used. the same code on the mbed compiler is 112kB.

To make it more clear i have for example a method (createLinesForPrint(...) code pasted beneath) which is costing like 5-6 kB of flash memory. For a full view of my code i published it under https://developer.mbed.org/users/anteoc/code/teo_test_2/. Any help or code snippets to make my code better are really appreciated. Please mind i am also learning c/c++ with this project so i am sure the code is in some parts not as one would expect for good programming practisies in this language

LINE_LENGTH = 40;
std::string createLineWithSpace_Middle(std::string textLeft, std::string textRight) {
	std::string line;

	//add left text
	line += textLeft;

	//add space in the middle
	int middleLength = LINE_LENGTH - textLeft.size() - textRight.size();
	for (int i = 0; i < middleLength; i++) {
		line += " ";
	}


	//add left right
	line += textRight;

	return line;
}
std::string createLineWithText_Middle(std::string textToShow) {
	int textToShowSize = textToShow.size();
	int dif = LINE_LENGTH - textToShowSize;
	int partLength = dif / 2;

	std::string line;
	int totalChars = 0;
	//first half of empty chars
	for (int i = 0; i < partLength; i++) {
		line += " ";
		totalChars++;
	}

	//the text to show
	line += textToShow;
	totalChars += textToShowSize;

	//second half of empty chars
	for (int i = 0; i < partLength; i++) {
		line += " ";
		totalChars++;
	}

	//last char. happens when dif is odd
	if (totalChars == 39) {
		line += " ";
		totalChars++;
	}

	return line;
}

std::vector<std::string> createLinesForPrint(Invoice * invoice) {
	std::string emptyLine = createEmptyLine();
	std::vector<std::string> lines;

	lines.push_back(createLineWithText_Middle(header));
	lines.push_back(createLineWithText_Middle(fiscalnr));
	lines.push_back(createLineWithText_Middle(vatnr));
	lines.push_back(createLineWithText_Middle("------------------------------"));

	std::vector<std::string> elements = split4(extraHeader, '\n');
	for (vector<string>::iterator it = elements.begin(); it < elements.end(); it++) {
		std::string headerLine = *it;
		lines.push_back(createLineWithText_Middle(headerLine));
	}

	lines.push_back(createLineWithSpace_Middle(invoice->documentFirstPart.deviceNrInShop, langText[3]));
	lines.push_back(createLineWithSpace_Middle("", invoice->documentFirstPart.operatorsName + " " + invoice->documentFirstPart.operatorsCode));

	for (vector<SaleItem*>::iterator it = invoice->saleItems.begin(); it < invoice->saleItems.end(); it++) {
		SaleItem * si = *it;
		vector<std::string> siLines = createLinesForPrint(si);
		for (vector<string>::iterator si_it = siLines.begin(); si_it < siLines.end(); si_it++) {
			std::string si_line = *si_it;
			lines.push_back(si_line);
		}
	}

	if (invoice->percentageDiscount != (double)0 || invoice->absoluteDiscount != (double)0) {
		lines.push_back(emptyLine);
		lines.push_back(createLineWithSpace_Middle(langText[4], convertToString(invoice->totalAfterDiscount_ForSaleItems, FORMAT_FOR_MONEY_VALUE_PRINT)));
		lines.push_back(emptyLine);
	}
	if (invoice->percentageDiscount != (double)0) {
		decimal2 discountValue = invoice->percentageDiscount/(double)100 * (double)invoice->totalAfterDiscount_ForSaleItems;
		if (invoice->percentageDiscount > (double)0) {
			lines.push_back(createLineWithSpace_Middle(langText[5] + " " + convertToString(invoice->percentageDiscount, FORMAT_FOR_MONEY_VALUE_PRINT) + "%", convertToString(discountValue, FORMAT_FOR_MONEY_VALUE_PRINT)));
		} else {
			lines.push_back(createLineWithSpace_Middle(langText[6] + " " + convertToString(invoice->percentageDiscount, FORMAT_FOR_MONEY_VALUE_PRINT) + "%", convertToString(discountValue, FORMAT_FOR_MONEY_VALUE_PRINT)));
		}

	} else if (invoice->absoluteDiscount != (double)0) {
		if (invoice->absoluteDiscount > (double)0) {
			lines.push_back(createLineWithSpace_Middle(langText[5], convertToString(invoice->absoluteDiscount, FORMAT_FOR_MONEY_VALUE_PRINT)));
		} else {
			lines.push_back(createLineWithSpace_Middle(langText[6], convertToString(invoice->absoluteDiscount, FORMAT_FOR_MONEY_VALUE_PRINT)));
		}
	}

	lines.push_back(emptyLine);
	lines.push_back(createLineWithSpace_Middle(langText[10], convertToString(invoice->totalAfterDiscount_ForInvoice, FORMAT_FOR_MONEY_VALUE_PRINT)));
	lines.push_back(emptyLine);

	for (std::map<PaymentTypeEnum, decimal2>::iterator it = invoice->payments.begin(); it != invoice->payments.end(); it++) {
		std::string paymentTypeAsText;
		switch(it->first) {
		case 0 : paymentTypeAsText = langText[11]; break;
		case 1 : paymentTypeAsText = langText[12]; break;
		case 2 : paymentTypeAsText = langText[13]; break;
		case 3 : paymentTypeAsText = langText[14]; break;
		default : paymentTypeAsText = langText[11];
		}

		lines.push_back(createLineWithSpace_Middle(
				paymentTypeAsText,
				convertToString(it->second, FORMAT_FOR_MONEY_VALUE_PRINT)
		));
	}

	for (std::map<char, TaxGroupTotal>::iterator it = invoice->taxTotals.begin(); it != invoice->taxTotals.end(); it++) {
		if (it->second.taxPerTaxGroup != (double)0) {
			lines.push_back(createLineWithSpace_Middle(
					langText[18] + " " + convertToStringFromChar(it->first) +" " + convertToString(it->second.taxGroupPercentageValue, FORMAT_FOR_VAT_VALUE_PRINT) + "%",
					convertToString(it->second.taxPerTaxGroup, FORMAT_FOR_MONEY_VALUE_PRINT)
					));
		}
	}

	lines.push_back(createLineWithSpace_Middle(langText[22], convertToString(invoice->totalWithoutTax, FORMAT_FOR_MONEY_VALUE_PRINT)));

	lines.push_back(emptyLine);
	lines.push_back(createLineWithSpace_Middle(convertToStringFromInt(invoice->documentFinalPart.nrOfDocsPrinted_Total), langText[59] + ": " + convertToStringFromInt(invoice->saleItems.size())));
	lines.push_back(createLineWithSpace_Middle(invoice->documentFinalPart.date, invoice->documentFinalPart.time));
	lines.push_back(createLineWithSpace_Middle(langText[60], invoice->documentFinalPart.serialNumber));
	lines.push_back(emptyLine);
	lines.push_back(createLineWithText_Middle(langText[61] + " " + convertToStringFromInt(invoice->documentNumberInReport)));
	lines.push_back(emptyLine);

	elements = split4(extraFooter, '\n');
	for (vector<string>::iterator it = elements.begin(); it < elements.end(); it++) {
		std::string headerLine = *it;
		lines.push_back(createLineWithText_Middle(headerLine));
	}

	lines.push_back(createLineWithText_Middle("Logo Fiskale")); //TODO: lang
	lines.push_back(createLineWithText_Middle(langText[64]));
	lines.push_back(createLineWithText_Middle(langText[65]));

	return lines;
}

2 Answers

7 years, 12 months ago.

From Andy's comment:

Quote:

It looks like you had an array of constant std:strings, changing that to an array of c style char * strings will save a small amount, it looks like the std::string overhead is between 1 and 26 bytes depending on library implementation and string length.

Changing a few to char arrays won't do alot indeed. But changing all of them to char arrays will give a large reduction in flash used.

If you ask on stackexchange if you should use an array or a vector, they will all tell you that you should use vectors and arrays are bad. However just including the vector lib already does not fit on the smaller mbed MCUs, and they are already larger than the 8-bit MCUs. Now you have a mid-sized one regarding flash, but still those standard libs eat a crazy amount of memory.

So option A: Switch to arrays, char arrays, and if needed make a custom class for something like a vector. Option B: Use a larger MCU.

Accepted Answer

Ok i tried during these days to remove all string usages and changing them to char * and also change part of my program and until now i won about 30 kb.

posted by anteo c 02 May 2016
7 years, 12 months ago.

In your question you are mixing RAM en FLASH problems and solutions.
-FLASH is mainly depending on the C libraries...if you use strings, vectors and floating point you need about 150k . -when you use many objects the nanolib does a very efficient use of RAM memory but is slower (much more cpu cycles but much less ram). You find the nanolib with the offline compiler gccarm
There is a obvious choice: a processor with 256k flash and more RAM.
Look at the Teensy !

Hi Robert,

thanks for your answer. Well for now i am not having any RAM problems. The ram usage when built (based on the online mbed compiler) is at 4kB and when the program runs i haven't got yet any problems. The main problem is that the flash is almost all used so i hoped maybe there are possibilities to write part of my code in different way so to win some flash memory. i would be happy if i could win like 50kB (i do not know if this is possible).

posted by anteo c 25 Apr 2016

It looks like you had an array of constant std:strings, changing that to an array of c style char * strings will save a small amount, it looks like the std::string overhead is between 1 and 26 bytes depending on library implementation and string length. Still that's not going to save more than 1k or so unless I've missed a lot of string constants somewhere.

You have some strings #defined as macros. If those macros are used in more than one place then replacing them with const char* rather than macros may also save a little bit of space by avoiding duplicates.

Other than that the best way to save code would be to avoid using the std:: libraries. They are really helpful and on a desktop system the 50kB of code they add is nothing but in the embedded world they eat up a lot of code space. The flash usage is the same if you use them once or 100 times so it is an all or nothing thing.

Also don't trust the compiler to give accurate RAM usage. It's only counting globals and static memory. The space taken up by the stack or any dynamic memory won't be taken into account. e.g. a std::vector<std::string> will take up a few dozen bytes according to the compiler. As soon as you start to add strings to it that will go up a lot.

posted by Andy A 25 Apr 2016